One small API to validate, parse, and format 250+ standard identifier numbers in Clojure. It covers IBAN/BIC, credit cards, ISBN/ISSN/ISIN, and national ID, VAT/GST, and tax numbers for 80+ countries.
Try it live - a hosted validator over this library. Paste a number to validate it, to parse it, or to detect its format. The validator also decodes GS1-128 barcodes and checks EU VAT numbers against the live VIES registry. It keeps numbers in memory only and stores none of them.
Unofficial, community-maintained. Not affiliated with Apache, iban4j, or any card network.
Clojure has many one-identifier libraries: an IBAN parser here, a Luhn checker there. Most
of them are small and unmaintained, and each one has its own API. No single library validated
the common, checksummable identifiers behind one consistent interface, the way
Python's python-stdnum does. stdnum-clj is that
facade, and it now covers the full catalogue of number formats in python-stdnum.
For the international identifiers, this library wraps the maintained Apache Commons Validator and iban4j engines instead of writing them again. Those checks stay as correct as the two engines when the engines get updates. This library implements the national and tax standards that have public, documented algorithms clean-room. It keeps them under its EPL license.
deps.edn:
net.clojars.savya/stdnum-clj {:mvn/version "0.31.1"}
Leiningen / Boot:
[net.clojars.savya/stdnum-clj "0.31.1"]
(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]
;; metadata - inspect types and their cited valid examples
(stdnum/type-category :de-vat) ;=> :vat
(stdnum/type-country :de-vat) ;=> :de
(stdnum/example :de-vat) ;=> "DE136695976"
(stdnum/examples :de-vat) ;=> ["DE136695976"]
(stdnum/describe :de-vat) ;=> {:type :de-vat, :category :vat, :country :de, :example "DE136695976", :source "..."}
;; 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:
(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)
stdnum.gs1-128 decodes GS1-128 (UCC/EAN-128) Application Identifier element strings. These
strings hold the data on logistics and retail barcodes. The parser accepts the parenthesized
form and the raw FNC1 form:
(require '[stdnum.gs1-128 :as gs1])
(gs1/parse "(01)09521234543213(3103)000123(10)ABC123")
;=> [{:ai "01" :label "GTIN" :value "09521234543213"}
; {:ai "3103" :label "NET WEIGHT (kg)" :value "000123" :decimals 3 :decimal-value 0.123}
; {:ai "10" :label "BATCH/LOT" :value "ABC123"}]
(gs1/parse-map "(01)09521234543213(10)ABC123") ;=> {"01" "09521234543213", "10" "ABC123"}
A checksum proves that a VAT number is well-formed. It cannot prove that 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, and so on) returns {:error "..."}.
It does not return a misleading :valid? false. When the registry cannot answer, the validity is unknown. This
namespace is the only part of the library that does network I/O. It requires JDK 11+ (it uses
java.net.http) and it adds 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 · :cz-bankaccount · :nz-bankaccount · :iso11649 · :ar-cbu · :es-ccc · :be-ogm · :ch-esr · :no-kontonr · :eu-at02 |
| Securities | :isin · :lei · :cusip · :sedol · :de-wkn · :figi · :cfi |
| Publishing / media / device | :isbn · :issn · :ismn · :iswc · :grid · :isan · :eu-banknote · :imei · :luhn · :isrc · :isil · :mac · :imsi · :meid · :bitcoin |
| Commerce / logistics / vehicle / industry | :ean13 · :ean8 · :upc · :gtin14 · :sscc · :gln · :iso6346 · :upu-s10 · :vin · :imo · :cas · :nhs · :npi · :it-aic · :eu-eic · :eu-ecnumber · :eu-excise · :eu-nace · :es-cae · :es-cups · :es-postalcode · :at-postleitzahl · :nl-brin · :nl-postcode · :se-postnummer |
| Research / name | :orcid · :isni |
| National & tax IDs - Europe | :gb-nino · :es-dni · :es-nie · :es-nif · :es-referenciacatastral · :nl-bsn · :nl-identiteitskaartnummer · :nl-onderwijsnummer · :se-pnr · :no-org · :no-fodselsnummer · :pt-nif · :pt-cc · :cz-ico · :hr-oib · :it-cf · :ch-uid · :ch-ahv · :be-nn · :be-bis · :be-ssn · :be-eid · :fi-hetu · :fr-nir · :fr-nif · :fr-accise · :pl-pesel · :ie-pps · :ee-ik · :lt-asmens · :si-emso · :ro-cnp · :ro-cf · :ro-cui · :ro-onrc · :cz-rc · :sk-rc · :gr-amka · :bg-egn · :bg-pnf · :ru-inn · :ru-snils · :ua-edrpou · :ua-rntrc · :is-kennitala · :ru-ogrn · :rs-pib · :me-pib · :mk-edb · :pl-regon · :sk-ico · :ee-rk · :fr-siren · :fr-siret · :fr-rcs · :se-orgnr · :es-cif · :md-idno · :by-unp · :si-maticna · :ad-nrt · :al-nipt · :li-peid · :sm-coe · :gb-utr · :gb-upn · :dk-cvr · :dk-cpr · :fi-ytunnus · :fi-associationid · :fi-veronumero · :de-idnr · :de-handelsregisternummer · :de-leitweg · :de-stnr · :az-voen · :at-businessid · :at-tin · :at-vnr · :jmbg |
| National & tax IDs - Americas | :us-ssn · :us-ein · :br-cpf · :br-cnpj · :ca-sin · :ca-bcphn · :ar-cuit · :ar-dni · :cl-rut · :co-nit · :pe-ruc · :pe-cui · :cr-cpf · :cr-cpj · :cr-cr · :ec-ced · :mx-curp · :ve-rif · :do-rnc · :do-cedula · :do-ncf · :uy-rut · :ec-ruc · :py-ruc · :gt-nit · :mx-rfc · :ca-bn · :cu-ni · :sv-nit · :us-itin · :us-atin · :us-ptin · :us-tin |
| National & tax IDs - Asia-Pacific | :au-abn · :au-tfn · :in-pan · :in-aadhaar · :in-epic · :in-vid · :cn-ric · :jp-cn · :jp-in · :nz-ird · :sg-nric · :sg-uen · :hk-id · :kr-brn · :kr-rrn · :tw-gui · :cn-usci · :vn-mst · :au-acn · :nz-nzbn · :id-npwp · :id-nik · :th-moa · :th-pin · :th-tin · :kz-bin · :my-nric · :pk-cnic |
| National & tax IDs - Africa & M. East | :za-id · :za-tin · :tr-tc · :il-idnr · :il-company · :tr-vkn · :mu-nid · :mz-nuit · :ke-pin · :dz-nif · :eg-tn · :gh-tin · :gn-nifp · :ma-ice · :tn-mf · :sn-ninea |
| VAT / GST (EU-27 complete) | :de-vat · :fr-vat · :mc-tva · :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 · :mt-vat · :sk-vat · :lt-vat · :cy-vat · :ro-vat · :es-vat · :ie-vat · :nl-vat · :lv-vat · :bg-vat · :hr-vat · :cz-vat · :pt-vat · :in-gstin · :eu-oss · :ch-vat · :no-mva · :fo-vn · :is-vsk · :vatin · :eu-vat |
This library wraps the international identifiers from Commons Validator and iban4j. It implements the global and national standards that have public, documented algorithms (LEI, VAT, CPF/CNPJ, SSN, and so on) clean-room, and keeps them under its EPL license. To get more formats, open an issue for the identifier you need.
A cited corpus sets correctness, not ad-hoc assertions. resources/stdnum/vectors.edn maps
every one of the identifier types to {:valid [...] :invalid [...] :source "..."}, and :source
is mandatory. The corpus ships in the jar and supplies the data for example, examples, and
describe. Each vector comes from one of these sources:
The test suite runs from this file. To add a format, add a cited vector first.
The tests verify every checksummed type against a check digit that they compute independently.
If a type is purely structural (it has a format, an embedded date, or a component
code, but no check digit), the :source says so. If no published example exists, the corpus cites
a number built from the published algorithm and marks it constructed.
For EU VAT, entries with the tag :vies true are companies with a confirmed live registration.
The clojure -M:test suite skips these network-backed rechecks. A valid checksum does not make a
number registered: several common example VAT numbers have a valid checksum but no registration,
and the corpus labels them as algorithm examples.
Copyright © 2026 Savyasachi
Distributed under the Eclipse Public License 2.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 |