Unified validation, parsing, and formatting of standard identifier numbers for Clojure - credit cards, IBAN/BIC, ISBN, ISSN, ISIN, US bank routing (ABA), IMEI, and the raw Luhn check, behind one small API.
Unofficial, community-maintained. Not affiliated with Apache, iban4j, or any card network.
Clojure has plenty of one-identifier libraries (an IBAN parser here, a Luhn checker there),
most of them tiny and unmaintained, each with its own API. There was no single library that
validates the common, checksummable identifiers under one consistent interface - the way
Python's python-stdnum does. stdnum-clj is that facade. For the international identifiers it
wraps the maintained Apache Commons Validator
and iban4j engines rather than reinventing them, so
those checks are as correct as those libraries and stay correct as they're updated. A few global
and national standards with public, well-documented algorithms (LEI, Brazil CPF/CNPJ) are
implemented clean-room and kept under this library's EPL license.
Leiningen / Boot:
[net.clojars.savya/stdnum-clj "0.11.0"]
deps.edn:
net.clojars.savya/stdnum-clj {:mvn/version "0.11.0"}
(require '[stdnum.core :as stdnum])
;; valid? - dispatch on an identifier-type keyword (any of `stdnum/types`)
(stdnum/valid? :iban "GB82 WEST 1234 5698 7654 32") ;=> true
(stdnum/valid? :credit-card "4111 1111 1111 1111") ;=> true (separators tolerated)
(stdnum/valid? :de-vat "DE136695976") ;=> true (country prefix optional)
(stdnum/valid? :iban "GB82 WEST 1234 5698 7654 33") ;=> false (bad check digit)
;; parse - validity plus extracted fields where they exist
(stdnum/parse :credit-card "378282246310005") ;=> {:valid? true, :network :amex, :iin "378282", :last4 "0005"}
(stdnum/parse :iban "GB82WEST12345698765432")
;=> {:valid? true, :country "GB", :bban "WEST12345698765432",
; :bank-code "WEST", :branch-code "123456", :account-number "98765432", :formatted "GB82 WEST ..."}
;; some national IDs embed structured data - parse pulls it out
(stdnum/parse :mx-curp "HEGG560427MVZRRL04")
;=> {:valid? true, :birth-date "1956-04-27", :gender :female, :state "VZ", :state-name "Veracruz"}
(stdnum/parse :za-id "8001015009087")
;=> {:valid? true, :gender :male, :citizen true, :birth-date "1980-01-01"}
;; format - canonical human form, or nil if invalid
(stdnum/format :br-cnpj "11222333000181") ;=> "11.222.333/0001-81"
;; detect - which types consider a value valid
(stdnum/detect "4111111111111111") ;=> [:credit-card :luhn]
;; helpers
(stdnum/card-network "6011111111111117") ;=> :discover
stdnum/types ;=> #{:iban :credit-card :de-vat ...} (the full set)
valid?, parse, and format throw IllegalArgumentException only on an unknown
identifier type (a programming bug). Bad data never throws: valid? returns false,
parse returns {:valid? false}, format returns nil.
When you need the raw algorithm rather than a typed validator, stdnum.checkdigit exposes them
directly (the python-stdnum stdnum.luhn / verhoeff / iso7064 parallel):
(require '[stdnum.checkdigit :as cd])
(cd/luhn-valid? "79927398713") ;=> true
(cd/luhn-check-digit "7992739871") ;=> "3"
(cd/verhoeff-check-digit "23412341234") ;=> "6"
(cd/iso7064-mod11-2-check "000000021825009") ;=> "7" (ORCID/ISNI check char, may be "X")
(cd/iso7064-mod97-10-valid? "5493001KJTIIGC8Y1R12") ;=> true (LEI / IBAN family)
A checksum proves a VAT number is well-formed; it can't prove the company exists. stdnum.vies
checks a number against the EU's live VIES registry:
(require '[stdnum.vies :as vies])
(vies/check "LU26375245")
;=> {:valid? true, :country "LU", :vat-number "26375245",
; :name "AMAZON EUROPE CORE S.A R.L.", :address "38, AVENUE JOHN F. KENNEDY...", ...}
A member-state outage (MS_UNAVAILABLE, rate-limiting, …) returns {:error "..."} rather than a
misleading :valid? false - validity is genuinely unknown when the registry can't answer. This is
the only part of the library that does network I/O; it lives in its own namespace, requires JDK
11+ (uses java.net.http), and pulls in org.clojure/data.json. stdnum.core stays pure.
stdnum/types is the authoritative set. National identifiers are keyed by an ISO-3166 prefix
(:br-cpf, :us-ssn, :de-vat); full descriptions are on cljdoc.
| Category | Types |
|---|---|
| Banking & cards | :credit-card (+ network) · :iban · :bic · :aba · :mx-clabe |
| Securities | :isin · :lei · :cusip · :sedol · :figi |
| Publishing / device | :isbn · :issn · :ismn · :imei · :luhn |
| Commerce / vehicle / industry | :ean13 · :ean8 · :upc · :gtin14 · :sscc · :gln · :vin · :imo · :cas · :nhs · :npi |
| Research / name | :orcid · :isni |
| Tax & national IDs | :us-ssn · :us-ein · :gb-nino · :br-cpf · :br-cnpj · :ca-sin · :au-abn · :au-tfn · :in-pan · :in-aadhaar · :es-dni · :es-nie · :nl-bsn · :cn-ric · :se-pnr · :za-id · :no-org · :tr-tc · :pt-nif · :cz-ico · :jp-cn · :hr-oib · :it-cf · :ch-uid · :ch-ahv · :nz-ird · :be-nn · :fi-hetu · :sg-nric · :hk-id · :kr-brn · :fr-nir · :pl-pesel · :ar-cuit · :cl-rut · :co-nit · :pe-ruc · :ie-pps · :ee-ik · :jmbg · :ec-ced · :bg-egn · :mx-curp |
| VAT / GST | :de-vat · :fr-vat · :it-vat · :be-vat · :pl-vat · :gb-vat · :at-vat · :dk-vat · :fi-vat · :se-vat · :gr-vat · :lu-vat · :si-vat · :ee-vat · :hu-vat · :in-gstin · :mt-vat · :sk-vat · :lt-vat · :cy-vat · :ro-vat · :es-vat · :ie-vat |
International identifiers are wrapped from Commons Validator / iban4j; global and national standards with public, well-documented algorithms (LEI, VAT, CPF/CNPJ, SSN, …) are implemented clean-room and stay under this library's EPL license. More are added on demand - open an issue for an identifier you need.
Copyright (c) 2026 Savyasachi. Released under the Eclipse Public License 1.0.
Can you improve this documentation?Edit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |