Generator · Luxembourg data

Luxembourg matricule

Endpoint GET /v1/lu/matricule

What it covers

Returns a valid Luxembourg matricule (national identification number) with correct check digits. The matricule encodes the full birth date (but no sex); constrain it with age/birth-date filters. Contradictory combinations are rejected with a 400. Set `invalid=true` for deliberately wrong check digits.

How it’s calculated

The Luxembourg matricule carries TWO trailing check digits over the same 11-digit payload: a Luhn (mod-10) digit at position 12, then a Verhoeff (dihedral D5) digit at position 13.

Algorithm

  1. Build the 11-digit payload: full 4-digit birth year YYYY, then 2-digit month MM, then 2-digit day DD, then a 3-digit serial (000-999), zero-padded. Result is exactly 11 digits, e.g. 19870324042.
  2. Convert the 11-character string to an array of digit values 0-9 (leading zeros are significant and preserved).
  3. Compute C1 = the Luhn (mod-10) check digit over those 11 payload digits: walk the digits from RIGHT to LEFT with a doubling flag that STARTS ON (the rightmost payload digit is doubled first). For each digit, if the flag is on multiply it by 2 and, if the product exceeds 9, subtract 9; otherwise take the digit as-is. Add each resulting value to a running sum, and flip the doubling flag after every digit.
  4. C1 = (10 - (sum mod 10)) mod 10.
  5. Compute C2 = the Verhoeff (dihedral group D5) check digit over the SAME 11 payload digits (NOT over the first 12 — the Luhn digit is excluded). Use the standard multiplication table D[10][10], permutation table P[8][10], and inverse table INV[10] shown below.
  6. Reverse the 11 payload digits. Set check = 0. For each pair (index, digit) of the reversed list, where index is 0-based, update check = D[check][ P[(index + 1) mod 8][digit] ].
  7. After consuming all 11 digits, C2 = INV[check].
  8. The final 13-digit matricule is the 11 payload digits, followed by C1 (position 12), followed by C2 (position 13).
  9. Tables — D (rows 0..9): [0,1,2,3,4,5,6,7,8,9],[1,2,3,4,0,6,7,8,9,5],[2,3,4,0,1,7,8,9,5,6],[3,4,0,1,2,8,9,5,6,7],[4,0,1,2,3,9,5,6,7,8],[5,9,8,7,6,0,4,3,2,1],[6,5,9,8,7,1,0,4,3,2],[7,6,5,9,8,2,1,0,4,3],[8,7,6,5,9,3,2,1,0,4],[9,8,7,6,5,4,3,2,1,0]. P (rows 0..7): [0,1,2,3,4,5,6,7,8,9],[1,5,7,6,2,8,3,0,9,4],[5,8,0,3,7,9,6,1,4,2],[8,9,1,6,0,4,3,5,2,7],[9,4,5,3,1,2,6,8,7,0],[4,2,8,6,5,7,3,9,0,1],[2,7,9,3,8,0,6,4,1,5],[7,0,4,6,9,1,3,2,5,8]. INV: [0,4,3,2,1,5,6,7,8,9].

Worked example

Matricule for birth date 1987-03-24, serial 042 → payload 19870324042, check digits ??

Payload digits: 1 9 8 7 0 3 2 4 0 4 2 (YYYY=1987, MM=03, DD=24, serial=042).
LUHN (C1), right-to-left, doubling flag starts ON: digit 2 (double→4), 4 (→4), 0 (double→0), 4 (→4), 2 (double→4), 3 (→3), 0 (double→0), 7 (→7), 8 (double→16→7), 9 (→9), 1 (double→2).
Sum = 4+4+0+4+4+3+0+7+7+9+2 = 44. C1 = (10 - (44 mod 10)) mod 10 = (10 - 4) mod 10 = 6.
VERHOEFF (C2) over same 11 digits. Reversed payload: 2 4 0 4 2 3 0 7 8 9 1. check starts 0, update check = D[check][P[(index+1)%8][digit]].
i=0 d=2: P[1][2]=7, D[0][7]=7. i=1 d=4: P[2][4]=7, D[7][7]=0. i=2 d=0: P[3][0]=8, D[0][8]=8. i=3 d=4: P[4][4]=1, D[8][1]=7. i=4 d=2: P[5][2]=8, D[7][8]=4.
i=5 d=3: P[6][3]=3, D[4][3]=2. i=6 d=0: P[7][0]=7, D[2][7]=9. i=7 d=7: P[0][7]=7, D[9][7]=2. i=8 d=8: P[1][8]=9, D[2][9]=6. i=9 d=9: P[2][9]=2, D[6][2]=9. i=10 d=1: P[3][1]=9, D[9][9]=0.
Final check state = 0, so C2 = INV[0] = 0.
Check digits are C1 C2 = 6 0.

C1 = 6 (Luhn), C2 = 0 (Verhoeff); the full 13-digit matricule is 1987032404260.

Edge cases

  • The payload always uses a FULL four-digit year (YYYY, e.g. 1987), and the generator clamps encodable birth dates to the 1900-2099 range (MATRICULE_MIN_DATE / MATRICULE_MAX_DATE in build-matricule.ts).
  • The serial is a 3-digit field 000-999, zero-padded; the whole payload is always exactly 11 digits and the final matricule is always exactly 13 digits.

Gotchas

  • Both check digits are computed over the SAME first 11 digits. The Verhoeff digit (C2, position 13) is NOT computed over the 12 digits including the Luhn digit — it ignores C1 entirely.
  • The check-digit Verhoeff uses P[(index + 1) mod 8][digit] with a 0-based index over the reversed payload; the VALIDATION variant (isValidVerhoeff) instead uses P[index mod 8][digit] over all 13 digits and expects a final state of 0. Do not mix the two index offsets.
  • Luhn here uses the check-generation convention where the doubling flag starts ON at the rightmost payload digit (because the payload excludes the check digit). The separate isValidLuhn helper starts the doubling flag OFF because it walks a full number whose last digit is already the check digit.

Parameters

ParameterTypeDescription
atAgeintegerExact age in years at request time.
bornAfterstringpattern ^\d{4}(?:-\d{2}(?:-\d{2})?)?$Born after this date or fragment (YYYY, YYYY-MM, YYYY-MM-DD).
bornBeforestringpattern ^\d{4}(?:-\d{2}(?:-\d{2})?)?$Born before this date or fragment (YYYY, YYYY-MM, YYYY-MM-DD).
bornOnstringpattern ^\d{4}(?:-\d{2}(?:-\d{2})?)?$Born on a date or within a fragment: YYYY, YYYY-MM, or YYYY-MM-DD.
countinteger1–1000Number of records to return, 1–1000 (your plan may lower this). Omitted returns a single record; set it for a batch.
edgebooleanRestrict output to edge-case values.
extremebooleanReturn a correct value wrapped in a hostile encoding (untrimmed whitespace, invisible/zero-width characters, BOM, or bidi/combining marks). Homoglyphs are excluded so the value stays machine-parseable; it still validates after normalisation.
invalidbooleanReturn a matricule with deliberately wrong check digits; the birth date stays valid.
olderThanintegerMinimum age in years at request time.
seedintegerInteger seed for reproducible output: the same seed always yields the same record.
youngerThanintegerMaximum age in years at request time.

Response fields

FieldTypeDescription
valuestringThe 13-digit matricule.
digitsstringThe canonical 13-digit matricule.
birthDatestringThe encoded birth date, ISO `YYYY-MM-DD`.

Other generators for Luxembourg

See all generators for Luxembourg →

The same kind of identifier elsewhere

Try it

Generate a live example below — every response is valid by format and entirely synthetic.