PackageTrack
Sign in Get early access

apenia_verify

Apenia Verify — KYC capture SDK. Native active-liveness selfie + document scan; matching runs server-side in Apenia Verify.

0.0.19 247 downloads/mo #1158 most downloaded on pub.dev

What this package is like to depend on

Last release 1 months ago

22 Jul 2026

Too new to tell

only 1 release windows

Nearly every release is documented

notes for 19 of 19 stable releases

Nothing withdrawn

no release was ever pulled

1 months old

19 releases · first in 2026

19 releases in the last 12 months

see the full history below

Release timeline

19 releases · Jul 2026 to Jul 2026
Release Pre-release

Releases

latest 19
  1. 0.0.19 22 Jul 2026
    Release notes
    • Passport enclosure widened to 2.4. Tuned against a real open passport on device — 2.4 fits the two-page spread comfortably while the MRZ stays large enough to read (confirmed reading correctly at this width).
    Open source →
  2. 0.0.18 22 Jul 2026
    Release notes
    • Wider passport enclosure. The passport capture frame was sized to a single ID-3 data page (1.586... 1.42), but a passport is presented open flat, so the data page sits inside a two-page spread. The frame is now wider (1.9) to fit the opened booklet without clipping the binding, and the guidance reads "hold it open flat".
    Open source →
  3. 0.0.17 22 Jul 2026
    Release notes
    • Refreshed the Apenia brand lockup. The plain apenia. wordmark is now a proper logo: a rounded brand-gradient badge with a verify check next to a clean "Apenia" — an identity product, stated in the mark. Shown in every screen's app bar.
    Open source →
  4. 0.0.16 22 Jul 2026
    Release notes
    • Verification wait is now a legible stepper. The single indefinite spinner became a two-step progress list — Uploading your captureMatching your face to your ID — each marked done / in-progress / upcoming, driven by the real SubmitPhase. A minute-long server wait reads as steady progress rather than a hang, with a "keep this screen open" reassurance.
    Open source →
  5. 0.0.15 22 Jul 2026
    Release notes
    • "Document captured" confirmation. After the ID scan, a small elegant modal card — floating over a dimmed scrim, with a success mark and a Continue button — confirms the milestone before moving to the selfie, instead of silently swapping screens. New reusable ApeniaModalCard, in Apenia red.
    Open source →
  6. 0.0.14 21 Jul 2026
    Release notes
    • Polished the document-choice list. National ID / Passport are now proper elevated list rows in a ListView — a soft shadow, a document-shaped icon chip (rounded, not a circle), a clearer chevron affordance, and a brand-tinted press ripple. The list scrolls cleanly instead of being pinned by a spacer.
    Open source →
  7. 0.0.13 21 Jul 2026
    Release notes

    Result screen: privacy-first, and a clear action per outcome.

    • No personal data or scores on the result screen. It previously showed the face-match / liveness percentages, the on-device check scorecard, and the extracted document fields (name, ID number, date of birth). Those are for your backend — via the verdict and the IPN — not for a screen an end user (or whoever is next to them) can read. The screen now shows only the decision, a short message, and the next action.
    • Three distinct outcomes.
      • Verified — filled success mark, "You're verified", Done.
      • Under review — amber, reassuring copy, Done.
      • Couldn't verify / didn't go through (rejected or failed) — a Try again button that restarts capture, plus a quiet Done.
    • onComplete fires on every terminal screen — success, review, declined, failed, and the still-processing screen — so the host app always receives the verdict when the user finishes, whichever outcome they landed on.
    Open source →
  8. 0.0.12 21 Jul 2026
    Release notes

    Fixes polling against the current Apenia API — upgrade. Verdicts were never being read, so the flow polled until timeout on every job.

    • Reads the schema_version: "1.0" response envelope. Production nests the verdict under verdict (decision, code, reason, confidence) and no longer sends top-level status/code/decision. The SDK was reading the old top-level fields, found nothing, and treated every poll as non-terminal — so a completed job looked identical to a pending one and the flow spun until the poll deadline. Both shapes now parse (the flat one is kept for the mock and older hosted paths).
    • Fixes in-progress codes being mistaken for failures. 4010 (waiting for upload) and 4020 (scoring) share the 4xxx family with real failures; the old family bucket mapped them to failed, which would stop polling and show a failure on a job that was merely still running. Now 4010 → pending, 4020 → processing (keep polling), 4030 → expired, 9010 → rejected, matching the server's own overrides.
    • Verdict text now reads verdict.reason (v1.0) as well as the old message, and identity confidence reads verdict.confidence.

    Branch on VerifyVerdict.isApproved / .decision as before — the mapping is unchanged, only where it's read from.

    Open source →
  9. 0.0.11 21 Jul 2026
    Release notes

    UX around submission, and illustration-led onboarding screens.

    • Submission phases are now distinct. submitArtifacts reports its stage through an onPhase callback (creatingSessionuploadingverifying), and ApeniaOnboarding shows a matching screen for each. A multi-minute server wait no longer sits under a single "uploading" spinner.
    • Fixed: a still-processing job could render as a rejection. If polling hit the deadline while the server was still scoring, the SDK returned a non-terminal verdict and the result screen showed it as declined. A non-terminal outcome now routes to a dedicated "Verification in progress" screen — the real decision arrives via the IPN — and is never shown as a decline.
    • Observable polling. Each poll attempt is reported to onDiagnostic (poll N -> processing (code …)), and a poll timeout is logged distinctly.
    • Illustration-led instruction screens. The intro, document and selfie screens now lead with original Apenia-red hero illustrations (a verify shield, an ID card, a face-in-oval that previews the real capture oval) instead of a flat icon; the approved result uses a filled success mark. All drawn in Flutter — no bundled assets — and the capture-only flow is unchanged (still no gallery-upload path).
    Open source →
  10. 0.0.10 20 Jul 2026
    Release notes

    Selfie capture quality. 0.0.9 uploaded whichever frame happened to be current when the alignment gate tripped — which in practice meant a blink, a downward glance, or the darkest frame of the session. The face is the only region a matcher scores, so this was the weakest link in the whole flow.

    • Best-frame selection. Every aligned frame is now scored and the best one is uploaded as both the selfie and the face_chip, rather than the newest one. Weighting: 50% eye-openness, 20% frontality, 30% face exposure. If the best frame so far has closed eyes the capture waits briefly and prompts "Look at the camera", but never blocks indefinitely — a hard gate would strand anyone whose eyes read as narrow to the detector.
    • Waits for auto-exposure and white balance to converge. The gate could pass within a second of the camera opening, before AE/AWB had settled, so the darkest and most colour-cast frame of the session became the matching image. This matters disproportionately for darker skin, where an underexposed face loses most of its usable tonal detail.
    • Exposure is measured on the face, not the frame. A backlit subject leaves the face in shadow while the frame average looks perfectly fine.
    • Wider face crop. Both detectors return a box tight on the face that excludes hair, so the previous symmetric margin clipped the forehead. Now asymmetric — generous above, moderate at the sides and chin.
    • Fixed: the orientation fix in 0.0.9 was upscaling every frame 3× on iOS. UIGraphicsImageRenderer defaults to the screen scale, so normalising orientation re-rendered at 3× the pixel dimensions: pure interpolation, no added detail, ~4× the bytes, and a visibly soft face that matches worse than the original. A selfie was landing at 1.5 MB instead of ~380 KB.

    Android and iOS are now line-for-line equivalent here — same selection, same weights, same crop margins. The 0.0.9 rotation bug existed because the two platforms had drifted apart; closing that gap was deliberate.

    Note on measuring this

    If your Apenia deployment reports provider: face baseline, these improvements are not measurable from the score — an identical capture scored 30 to 61 across runs on that provider. Judge the change by looking at the uploaded images. See doc/platform-findings.md for the server-side issues found while integrating against production.

    Open source →
  11. 0.0.9 20 Jul 2026
    Release notes

    Fixes a severe capture bug in 0.0.8 — upgrade. Selfies were saved with the wrong orientation on both platforms, which fails server-side face match and liveness on a perfectly good capture.

    • Fixed: rotated selfies. On iOS the frame was built as .leftMirrored and saved without normalising — jpegData writes raw pixels plus an EXIF tag, and server decoders routinely ignore EXIF (PIL without exif_transpose, OpenCV). On Android ImageProxy.toBitmap() discards rotationDegrees entirely, so the frame was saved in sensor orientation with no EXIF hint at all. Both now rotate the pixels before writing. A sideways face reads as a failed liveness check ("possible photo or screen replay"), so this could decline every genuine user.

    New payload — all additive

    Existing client_analysis keys are unchanged; parsers built against 0.0.8 keep working.

    • face_chip.jpg — a tight crop of the face from the primary frame (40% margin, quality 0.95), uploaded as an extra artifact. The full selfie spends most of its pixels on background; this gives the matcher far more detail on the only region it scores, for a couple of hundred KB. Flagged in the payload as liveness.face_chip.

    • liveness.frames[] — face geometry per saved frame: yaw/pitch/roll, bounds, and eye/smile probabilities. Lets the server verify the prompted motion (eye-openness collapsing on a blink, smiling rising on a smile) instead of trusting challenges_passed. Bounds are normalised to top-left fractions on both platforms, so Vision's bottom-left origin and ML Kit's pixel rect don't leak into the contract.

    • account_match — the fake-account signal: the identity the customer registered with versus the identity the document asserts. Supply ApeniaOnboarding.expectedName / expectedIdNumber to enable it; the key is absent entirely otherwise. Names compare as token sets with case, accents and punctuation normalised, so ordering and a missing middle name don't read as a mismatch — subset ("Moses Gathecha" vs "MOSES GATHECHA WAKANYI") is the normal case. Apostrophes are stripped rather than spaced, so O'Brien and N'Dour don't falsely mismatch.

      disjoint is grounds for review, never automatic rejection — marriage, transliteration and registration typos all produce honest mismatches. And it proves only that the account matches the document: someone presenting a stolen genuine ID passes it perfectly. Face match is what stands there.

    Open source →
  12. 0.0.8 20 Jul 2026
    Release notes

    First release exercised end-to-end against the live Apenia API. Every fix below came from real traffic, not from the docs — several are cases where the live API and the published docs disagree.

    • Fixed: a valid response could crash the flow after a successful upload. The live API returns "code": "2000" as a string; the SDK read it with as num?, which throws on a type mismatch rather than yielding null. The upload succeeded, then parsing the result threw. code, identity_confidence and the check scores now accept a number or a string.
    • Fixed: rejections could read as successes. A rejected verification comes back with "status": "completed" alongside "code": "2000" / "decision": "Rejected". Branching on status would approve it. The SDK derives from code first (then decision, then status) — now verified against a real 2xxx response rather than inferred from the docs.
    • Fixed: scores were always blank. The docs document {"score": 0.95, "match": true}; the live API sends match_score / liveness_score / matched on a 0–100 scale. Neither documented field exists in practice, so every score parsed as null. Both shapes are now read, and anything above 1 is normalised as a percentage.
    • Fixed: ApeniaOnboarding sent an invalid flow. It used flow: "onboarding", which the API rejects with HTTP 422. It now defaults to biometric_kyc — the flow this screen actually performs — and is overridable via the new flow parameter.

    Uploads

    • Document images are ~85% smaller. The document frame is cropped out of a full-resolution still, so a captured ID was landing at ~5 MB per side — a measured archive was 11.1 MB, of which 10 MB was two ID photos, versus ~380 KB for a selfie. Images are now capped at 2000 px on the long edge (1400 for standard). The same capture now produces a 2.1 MB archive that uploads in under 7 s where 11.1 MB failed at 20.7 s. 2000 px is ~590 DPI for an ID-1 card, well above what MRZ OCR needs.
    • The upload is retried up to three times with a fresh connection and backoff. A multi-megabyte PUT from a handset is the most fragile call in the flow, and it previously had no resilience — one dropped packet ended the verification. Genuine server rejections (413, 422) are not retried.
    • Separate timeouts. uploadTimeout (3 min) and resultTimeout (3 min) no longer share the 30 s requestTimeout meant for small JSON calls.

    Configuration

    • BREAKING — baseUrl is gone. The SDK resolves the Apenia host itself; integrators shouldn't hardcode it. baseUrlOverride remains for tests, mocks and self-hosted deployments.
    • New: sessionEndpoint + authToken / authTokenProvider. Point the SDK at your integration backend's session endpoint and hand it the signed-in user's bearer token; sessionProvider is now optional and reserved for cases that need full control. Session responses are accepted bare or wrapped in a data envelope, and duplicate slashes in the path are collapsed (/v1//x 404s on most routers).
    • New: onDiagnostic. Receives artifact sizes, archive total, upload attempts, status codes and timings — the data needed to diagnose a capture failure on a real device, where debugPrint is stripped from release builds. Sizes and status codes only; never image bytes, tokens or personal data.
    • Error messages now read Laravel-style {"message": …, "errors": {…}} bodies from integration backends, not just Apenia's {"detail": …}.
    Open source →
  13. 0.0.7 19 Jul 2026
    Release notes
    • Sharpness on iOS. The focus proxy is now measured on both platforms with the same computation (mean absolute horizontal luma gradient over the centre of the frame), so the values are comparable across devices. Both platforms also sample it on every analysed frame, so a manual shutter tap still carries a reading. Note it is only available in DocumentCaptureMode.auto, where the analyser runs — treat it as missing rather than zero elsewhere.
    • Cleaned up the published example. It is now a single, minimal, real integration: base URL, publishable key, sessionProvider pointing at your backend, and ApeniaOnboarding. The fake capture platform and mock backend no longer ship with the package.
    Open source →
  14. 0.0.6 19 Jul 2026
    Release notes
    • Capture telemetry in ClientAnalysis. The payload now describes how the capture went, not only what it produced — behavioural features the server's model can learn from:
      • retake_count — how many captures the user rejected. Repeated retakes on one document is a stronger fraud signal than any single frame.
      • manual_shutter — auto-detect never fired and the user forced the shot. Correlates with screens, photocopies and damaged cards.
      • elapsed_ms — time on the capture screen; both tails are informative.
      • sharpness — the measured focus value at capture (Android), so a borderline-but-accepted frame is distinguishable from a crisp one.
    • README install now points at the published package rather than a path dependency, and documents exactly what the upload archive contains — the images and the on-device analysis ship together in one request.
    • The example shows production and demo wiring side by side behind a kUseMockBackend flag.
    Open source →
  15. 0.0.5 19 Jul 2026
    Release notes

    Verified the client against the official docs at https://verify.apenia.com/docs and corrected two real mismatches.

    • Branch on code, not on scores. VerifyVerdict now parses Apenia's code, decision and message, and derives its status from the code (1xxx approved · 2xxx rejected · 3xxx review · 4xxx failed), falling back to decision and then status. Previously the SDK inferred the outcome from the coarse status string alone, which the docs explicitly warn against — a high face-match score can still accompany a rejection. Added VerifyVerdict.isApproved and a VerifyStatus.rejected state distinct from failed.
    • BREAKING — ApeniaConfig.appKey is required again. Apenia requires the publishable X-App-Key alongside the session client_token for device reads; 0.0.3–0.0.4 sent only the bearer token and would have been rejected with 401 against the live API. The secret key still never touches the device.
    • Upload limits are enforced client-side (32 MB archive, 8 MB per file) so an oversized capture fails with a clear message instead of a server 413.
    • ApeniaApiException now exposes .detail (Apenia's {"detail": …} body) and .reason (401 auth · 404 not found · 410 expired · 413 too large · 422 invalid format).
    • The result screen shows Apenia's own message when present, and distinguishes a declined verification from a failed one.
    Open source →
  16. 0.0.4 19 Jul 2026
    Release notes

    Device-level pre-screening: the SDK now reports what it worked out locally, and scores it.

    • ClientAnalysis payload. Everything derived on-device — MRZ fields, liveness challenges satisfied, capture mode / aspect / sides, SDK and platform — is packaged and sent to the server inside the upload manifest (client_analysis). Set ApeniaConfig.analysisEndpoint to additionally POST it to an ML validation service; that call is best-effort and never blocks a verification.
    • On-device check scorecard (LocalChecks) with a weighted confidence, floored at 5% and capped at 97%:
      • MRZ check digits — full ICAO 9303 7-3-1 validation of the document number, date of birth, expiry and composite digits. Catches OCR misreads and altered data.
      • Front vs MRZ cross-check — the printed face is OCR'd and its ID number, date of birth and name compared against what the machine-readable zone encodes. Disagreement is a forgery signal. Skipped for single-page documents rather than counted as a failure.
      • Portrait present on the document, document in date, liveness challenges completed, all sides captured.
    • Liveness results are no longer discarded — which challenges the user satisfied now reach the server.
    • Auto-capture waits for focus. Text presence alone fired the shutter before autofocus settled, which hit dense pages (passports) hardest. Capture now requires a settled focus (isAdjustingFocus on iOS, a luma-gradient sharpness measure on Android), a 1.2 s dwell, and stability that only accrues while sharp. Android also nudges autofocus when a document appears.

    Scope, stated plainly: these are integrity and quality checks, not identity verification. The device cannot compare the selfie to the document portrait — neither ML Kit nor Vision expose face embeddings — so face matching, passive anti-spoofing and document authenticity remain server-side decisions. A high local confidence means the capture is internally consistent and usable, nothing more.

    Open source →
  17. 0.0.3 18 Jul 2026
    Release notes
    • ~23 MB smaller on Android. Switched from the bundled ML Kit artifacts (com.google.mlkit:face-detection, :text-recognition) to the unbundled Play Services variants. The models are now served by Google Play Services instead of being shipped in the APK. Measured on a release arm64 build, the SDK's footprint drops from +25.5 MB to +1.8 MB.
    • The plugin manifest now requests the face and ocr models at install time so the first capture doesn't stall on a download.
    • Breaking for offline/non-GMS devices: face detection and MRZ OCR now require Google Play Services. If you need the SDK to work without it, pin 0.0.2, which bundles the models.
    Open source →
  18. 0.0.2 18 Jul 2026
    Release notes
    • Fix dependency conflict. archive was pinned to ^3.6.1, which made the package unresolvable in any app depending on image 4.x (e.g. via flutter_native_splash). The constraint is now >=3.6.1 <5.0.0; verified building and passing tests against both 3.6.1 and 4.0.9.
    • Corrected stale documentation: the capture flows are device-tested on Android and iOS, and the architecture notes no longer refer to VisionKit or the old prep_upload contract.
    Open source →
  19. 0.0.1 18 Jul 2026
    Release notes

    Initial release — KYC capture SDK for Apenia Verify.

    • Single-entry onboarding flow (ApeniaOnboarding): intro → choose document → document capture → live selfie → upload → result, in Apenia branding.
    • Document capture with a fixed, document-shaped enclosure (ID-1 card and passport data-page ratios) on a contained camera. Built on CameraX (Android) and AVFoundation (iOS) rather than the OS document scanner, so there are no enhancement, filter or re-crop options; the image is cropped to the frame.
    • Confirm / retake step after each captured side.
    • Auto or manual capture via ApeniaConfig.documentCaptureMode. Auto only fires once the document is detected inside the frame; the shutter always works as a manual override.
    • On-device MRZ extraction (ML Kit on Android, Vision on iOS) parsing TD1 and TD3 into full name, ID number, date of birth, sex, nationality and expiry. Kenyan IDs yield both the serial number and the national ID number.
    • Active-liveness selfie with a controlled face-scanner: oval overlay and a positioning gate (centred, sized, frontal, eyes open) before randomised blink / turn / smile challenges. Challenges rotate if a signal won't trigger. Capture only succeeds if the full sequence completes with a face present throughout — there is no static-image path.
    • Upload client implementing the onboarding-session contract (create session → pre-signed upload → poll / IPN), with no secret key on the device.
    • Web/desktop safe: no top-level dart:io, so the package compiles for web.
    Open source →

Every package, every release, already written down.

The archive is open and free. Watching your own project is what we are building next.

Browse the archive