BEIDKit reads a Belgian eID card through an external USB CCID smart card reader on iOS/iPadOS, and returns typed identity, address, and photo data through a small async/await API. No UI coupling, no dictionaries of loosely typed strings as the primary interface.
- iOS/iPadOS 16.0+ — this is the practical floor for the whole SDK, since Apple's
native support for external USB CCID smart card readers via
CryptoTokenKitlanded in iPadOS 16. - An external USB CCID smart card reader.
- A Belgian eID card.
Building an app in Xcode?
In your app project:
- File → Add Package Dependencies…
- Paste
https://github.com/whisker-works/BEIDKit.gitin the Package URL field - Pick a version rule (e.g. "Up to Next Major Version" starting at
0.1.0) - Click Add Package, then check BEIDKit for your app's target.
Xcode wires everything up for you — nothing to hand-edit.
Building your own Swift package that depends on BEIDKit?
Add it to your package's
Package.swift:
let package = Package(
...
dependencies: [
.package(url: "https://github.com/whisker-works/BEIDKit.git", from: "0.1.0")
],
targets: [
.target(name: "YourTarget", dependencies: ["BEIDKit"])
]
)Once BEIDKit is added, Swift Package Manager doesn't automatically move you to newer
releases — it pins whatever version it first resolved (recorded in Package.resolved)
and keeps using that on every build, even after a newer version is tagged.
In Xcode:
- File → Packages → Update to Latest Package Versions.
In a Package.swift-based project
- run
swift package updatefrom the command line.
Either way, as long as your version rule allows it (e.g. from: "0.1.0" allows any
0.x release up to, but not including, 1.0.0), this picks up the latest matching
version automatically — no manifest changes needed.
import BEIDKit
let reader = EIDCardReader()
let card = try await reader.readCard { step in
print("Progress: \(step)")
}
print(card.identity.name, card.identity.firstName)
print(card.address.streetAndNumber, card.address.zip, card.address.municipality)
let photo: Data = card.photo // raw JPEG bytes — you decide how to render itreadCard() is the single entry point. The optional onProgress closure is a
lightweight escape hatch for apps that want to show "reading identity…" style UI; the
simplest call site is just try await EIDCardReader().readCard().
readCard() throws EIDCardError, which covers the reader/card not being present,
session failures, card communication failures (naming which step and status word
failed), and unreadable data:
do {
let card = try await EIDCardReader().readCard()
} catch let error as EIDCardError {
print(error.errorDescription ?? "Unknown error")
}The fields citizens will realistically always have, as named, typed (String)
properties:
| Property | Card field |
|---|---|
cardNumber |
Card number |
cardValidityDateBegin |
Card validity begin date |
cardValidityDateEnd |
Card validity end date |
cardDeliveryMunicipality |
Card delivery municipality |
nationalNumber |
National number (see Legal considerations) |
name |
Surname |
firstName |
First name(s) — up to two given names, space-separated |
middleName |
Initial of a third given name, if any (optional — not a "middle name" in the everyday sense; most people's names fit entirely in firstName) |
nationality |
Nationality |
placeOfBirth |
Place of birth |
dateOfBirth |
Date of birth |
gender |
Gender |
documentType |
Document type |
Access these directly as properties:
card.identity.cardNumber
card.identity.dateOfBirthEvery other tag the card exposes is still readable via additionalFields: [String: String], keyed by name — access by subscripting with the key from the table below:
card.identity.additionalFields["workPermit"] // String?
card.identity.additionalFields["cardEUStartDate"] // String?| Key | Card field |
|---|---|
nobleCondition |
Noble condition |
specialStatus |
Special status |
duplicate |
Duplicate indicator |
specialOrganisation |
Special organisation |
memberOfFamily |
Member of family |
dateAndCountryOfProtection |
Date and country of protection |
workPermit |
Work permit |
employerVATNumber1 |
Employer VAT number 1 |
employerVATNumber2 |
Employer VAT number 2 |
regionalFileNumber |
Regional file number |
brexitMention1 |
Brexit mention 1 |
brexitMention2 |
Brexit mention 2 |
cardAMention1 |
Card A mention 1 |
cardAMention2 |
Card A mention 2 |
cardEUStartDate |
Card EU start date |
chipNumber, photoDigest, and basicPublicKeyDigest are on the card too, but are raw
bytes/a hash digest rather than human-readable text, so BEIDKit drops them rather than
surfacing them as garbled strings.
| Property | Card field |
|---|---|
streetAndNumber |
Street and number |
zip |
ZIP code |
municipality |
Municipality |
card.address.streetAndNumber
card.address.zipRaw JPEG bytes (Data) — BEIDKit doesn't impose a rendering approach.
Fields like dateOfBirth and gender are exposed as raw strings exactly as decoded off
the card, not as typed Date/enum values — see Roadmap below.
Deliberately deferred to a (much) later version:
- Certificate reading (auth cert, sign cert, CA/root certs).
- PIN verification and any signing/authentication operations using the card's private keys.
- Verifying the government's digital signature over the identity/address files against the Belgian root CA.
- Typed
Date/enum representations of fields likedateOfBirthorgender— v1 exposes these as raw strings, exactly as read off the card.
Examples/EIDReaderDemo is a minimal SwiftUI app demonstrating
the API end-to-end against real hardware. See its own README for setup.
This SDK reads real personal data off a government-issued identity document, including
the Belgian national register number (identity.nationalNumber). Once BEIDKit hands
that data to your app, how it's handled is on you, not the SDK.
The national register number is a special case. Under Belgian law, processing it is prohibited by default — independently of, and more strictly than, GDPR. It requires explicit authorization from the National Register's Sectoral Committee, granted only for tasks of general interest recognized by law, not for commercial purposes (customer accounts, loyalty programs, membership records, etc.). Using it without that authorization carries real penalties.
More broadly, every other field this SDK exposes (name, address, date of birth, photo, etc.) is personal data under GDPR, and the usual obligations — lawful basis, data minimization, security, retention limits — apply to however your application stores or processes it.
This SDK reads and returns data; it has no opinion on what you do with it afterward, and nothing here is legal advice. Consult a lawyer before using this for anything beyond local development/testing.
MIT — see LICENSE.