Skip to content
Constaia

Validate an ID document in a registration form in 5 minutes

How to check, at registration time, that an ID card or passport is valid, belongs to the person signing up and meets the minimum age, without storing the image. With the widget and a few lines of backend code.

By Constaia team5 min read

Also in: Español

In a registration form (a race, a club membership, a summer camp) the ID document is usually an "upload a photo" field that nobody looks at until something goes wrong: the ID had expired, it belonged to a sibling, or the participant wasn't old enough for the category.

This article covers what to check, what can be checked deterministically, and how to set it up with Constaia in a few minutes: an upload field on your page and one call from your server. Without storing the image.

What to check on an ID document at registration

This isn't banking KYC. At registration time, four questions matter:

  1. Is it an accepted ID document? A national ID card, residence card or passport, not a driving licence, a health card or a photo of something else.
  2. Is it valid? Today or, better, on the event date.
  3. Does it belong to the person registering? Name, number and date of birth match what they typed in the form.
  4. Do they meet the age requirement? Adult for an open event, or within a category's age range.

What you can check without AI

Part of the job needs no model at all, just arithmetic:

  • Check characters. The Spanish DNI and NIE end in a letter computed as the number modulo 23 against a fixed table; for the NIE, the leading X, Y or Z becomes 0, 1 or 2 (Spanish Ministry of the Interior). We walk through it with code in Spanish DNI and NIE check letter. Many other countries' IDs have their own check digits.
  • The machine-readable zone (MRZ). European ID cards carry a TD1 MRZ with check digits for the document number, date of birth and expiry date, as defined in ICAO Doc 9303. If the digits don't add up, or the MRZ says one thing and the printed text another, something is wrong. More in ICAO 9303 MRZ.
  • Dates. Expiry against a reference date, and age computed from the date of birth.

What does need a model is reading a skewed phone photo with glare in any orientation, and classifying which document it is. That's why Constaia combines both: AI reads and classifies, code validates.

Consistent is not genuine

Matching check digits prove the data is internally consistent, not that the document is genuine. Constaia does no face matching and does not guarantee authenticity; it returns signals (for example, a likely photo of a screen) so a person can take a look.

The flow, in three parts

  1. Your page shows an upload field. The <constaia-upload> widget opens the camera on mobile, warns if the photo is blurry and merges both sides of an ID card into one image.
  2. Your server receives the file, adds the data it already has from the registration (name, number, date of birth) and calls Constaia with your key. The key never reaches the browser.
  3. Your server decides based on the verdict: confirm, reject with the reason, or hold for review.

Minute 1: the field on your page

register.html
<script type="module" src="https://cdn.jsdelivr.net/npm/@constaia/widget@0.1"></script>

<constaia-upload
  endpoint="/api/registrations/1234/document"
  expect="es_dni,eu_id_card,passport"
  lang="en"
></constaia-upload>

The expect attribute only guides the interface. Your server decides what is accepted.

Minutes 2 to 4: the call from your server

import { Constaia } from "@constaia/sdk";

const constaia = new Constaia(); // reads CONSTAIA_API_KEY

export async function checkDocument(file: Buffer, filename: string, registration: Registration) {
  return constaia.analyze(
    { file, filename },
    {
      expect: ["es_dni", "eu_id_card", "passport"],
      checks: {
        notExpired: true,
        minAgeYears: 18,
        referenceDate: registration.eventDate, // "2027-03-14": validity and age on the event day
        holder: {
          fullName: registration.fullName,
          birthDate: registration.birthDate, // "YYYY-MM-DD"
        },
      },
      storage: "none",
      language: "en",
      metadata: { registration_id: String(registration.id) },
    },
  );
}

holder compares with what the person typed, ignoring accents, allowing a different surname order and small typos. storage: "none" means the file is processed in memory and not stored.

Minute 5: decide

The response includes verdict.status and a list of reasons in the language you asked for:

VerdictWhat it meansWhat to do
validAccepted document, not expired, right holder, old enoughConfirm the registration
invalidA reason with error severity: expired, different holder, wrong document type, under the minimum age, check digits that don't matchDon't confirm; show the reason and let them upload another
reviewSomething prevents a confident decision: blurry photo, unreadable fieldAccept provisionally and have a person look at it

A typical invalid message is "Expired on 15/06/2020." Showing it to the person right away saves an email round trip.

Don't auto-reject review: most are imperfect photos of perfectly good documents.

Minors and categories

For minors, the approach changes:

  • If the document belongs to the parent or guardian, validate it with their details and min_age_years: 18.
  • If it belongs to the child, validate their document and, if the category has an upper limit, use max_age_years. With reference_date you compute the age on the date your rules say (for example, 31 December of the season).

What to keep (and what not to)

Article 5(1)(c) GDPR requires data to be limited to what is necessary, and the Spanish DPA has found that requiring and keeping an ID copy when identity can be checked without it breaches that principle (PS-00138-2025). We cover it in detail in ID card copies and the Spanish DPA.

To show you did the check, keeping this is usually enough:

  • the analysis id,
  • the verdict and the date,
  • the checked document number, if you need it for the licence or membership.

If you don't want Constaia to keep the extracted data either, add keep_results: false: you get the response once and it can't be retrieved later.

Not legal advice

Even if you don't keep the image, collecting the document is still processing personal data: you need a legal basis, to inform people, and a processing agreement with your provider. Check your case with your data protection officer.

Try it without spending credits

With a ck_test_… key the result depends on the file name: dni_valid.jpg returns a valid ID with fictitious data, dni_expired.jpg an expired one and blurry.jpg a review case. You can build the whole flow before touching real documents. The full guide, with Express and Laravel, is in ID document in a registration form.

Summary

  • At registration, four things matter: document type, validity, holder and age.
  • Check characters, the MRZ and dates are checked with code; AI is used to read and classify.
  • With an upload field and one server call you get a verdict and a reason on the spot.
  • Keep the result of the check, not a photo of the document.

To try it with your own form, create a free account: 150 documents a month and test keys that don't use credits.

Sources

  1. 01Spanish Ministry of the Interior — NIF/NIE check character calculation
  2. 02ICAO — Doc 9303, Part 5: TD1 size machine readable official travel documents
  3. 03Regulation (EU) 2016/679 (GDPR), EUR-Lex
  4. 04AEPD (Spanish DPA) — Decision PS-00138-2025 (Provincial Council of Pontevedra)