faceping.aidocs

Capacitor reference

Every public type in the FacePing Capacitor plugin: setup, enrolment, verification, the camera screen and results.

Package: @faceping/capacitor (npm, preview, under the preview tag). Starter app: faceping/capacitor-starter. Capacitor 8. Android 7 (API 24) or later, on 64-bit phones (arm64-v8a) and x86_64 emulators. iOS 16 or later with Swift Package Manager, or iOS 15.1 or later with CocoaPods (see iOS versions); Xcode 26 or later.

The plugin is a thin bridge over the native Android and iOS SDKs, so the face checks and results behave exactly as they do there. It works in any Capacitor 8 app, whatever the web framework: Ionic, Angular, React, Vue or plain TypeScript. FacePing runs on the phone, not in a web browser: in ionic serve or a desktop browser, every call throws an unavailable error.

Install#

npm install @faceping/capacitor@preview
npx cap sync

Then:

  • iOS: add NSCameraUsageDescription to ios/App/App/Info.plist (Xcode: the App target's Info tab, Privacy - Camera Usage Description). Without it, iOS stops the app when the camera starts.
  • Android: nothing to add. The FacePing library declares the CAMERA and INTERNET permissions.

iOS versions#

The FacePing iOS SDK needs iOS 15.1 or later.

  • Swift Package Manager (the default for new Capacitor 8 apps): Capacitor writes the app's iOS version into its package as a whole number, so iOS 15.1 becomes iOS 15 and the build fails with "requires minimum platform version 15.1". Set the App target's Minimum Deployments to iOS 16.0, then run npx cap sync ios.
  • CocoaPods: set the deployment target to 15.1 or later (platform :ios, '15.1' in the Podfile).

How the native SDKs get into your app#

  • Android: the plugin depends on ai.faceping:faceping-android from Maven Central. Gradle fetches it like any other dependency.
  • iOS, Swift Package Manager: the plugin's package depends on faceping/faceping-ios, a binary Swift package. Xcode downloads the framework from its GitHub release and checks its checksum.
  • iOS, CocoaPods: pod install downloads FacePing.xcframework.zip once from the faceping-ios release, checks its SHA-256 against the checksum pinned in the package, and vendors it. Offline or behind a proxy, set FACEPING_IOS_ZIP=/path/to/FacePing.xcframework.zip (checked the same way) or FACEPING_IOS_URL to a mirror before npx cap sync ios.

The simulator has no front camera: test the live check on an iPhone.

Setup#

import { FacePing } from '@faceping/capacitor';

// Once, early. Resolves at once and loads the face models in the background.
await FacePing.init({ sandboxKey: 'fp_test_…' });   // sandbox: faces enrolled on the device
// await FacePing.init({ deviceToken: 'fpd_…' });   // production: faces synced from your check-in list

// Activates the sandbox key now, so a key problem shows up before the first person arrives.
await FacePing.sync();

FacePing is a single object you can call from anywhere in the app. Calling init again with the same options is fine (for example after a live reload); with different options it throws (call close() first). It resolves to { isSandbox, nativeSdkVersion }.

A key in the wrong format throws a setup error from init. A revoked key, or a first run with no connection, shows up on the first call that needs the key, so call sync() at start-up.

Options#

Option Type Default Notes
sandboxKey string A fp_test_ key from Developers → Sandbox keys.
deviceToken string Production. A fpd_ token for one check-in list. Set this or sandboxKey, not both.
region 'eu' 'eu' Your account's region, which sets the API address. Today the only value is EU (https://api-eu.faceping.ai/).
apiBaseUrl string Overrides the region's address. UK and US regions are available on request: we give you the address to put here.
passiveLiveness boolean true Passive liveness on every check. Turn it off only if your app runs its own liveness check first.
autoSyncInterval number \| null 300 Production only, in seconds. null turns automatic sync off; call sync() yourself.

FacePing#

Call Returns Notes
enroll(id, photo?, camera?) EnrollResult Sandbox only. With no photo, opens the camera screen and enrols the photo the person takes. Production enrolment happens with consent on your server or through the FacePing widget. Replaces any face already stored under id.
verifyLive(id, camera?) VerifyResult A live check on the camera screen: match, a head turn in a random direction, match again. The screen shows the prompts and closes itself when the check is done. About 10 seconds at most.
verify(id, photo?, camera?) VerifyResult One photo, passive liveness only. With no photo, opens the camera screen.
forget(id) boolean Sandbox: deletes the face now; false if there was none. Production: throws invalidOperation (remove the person through your server).
forgetAll() Deletes every face on the device now. In production the next sync brings back the people still enrolled.
enrolledIds() string[] Who can be verified on this device, as of the last enrol, forget or sync.
sync() SyncResult Production: downloads the latest faces for the device's check-in list; a revoked device wipes its faces. Sandbox: activates the key, or renews its licence when due. Either way it refreshes enrolledIds(), so it's a good first call when the app starts.
close() Stops automatic sync and frees the engine. Call init again to start over.

Every call returns a promise.

The camera screen#

A web view can't hold a native camera view, so FacePing brings its own screen. When you call enroll or verify without a photo, or verifyLive, the plugin opens a full-screen native camera over your app, with a title, the oval face guide and a cancel button:

  • enroll and verify: the person lines up their face and taps Take photo.
  • verifyLive: the check starts at once, with the liveness prompts ("Turn your head left", "Look back at the camera").

When the check is done, the guide turns green or red for a moment and the screen closes itself; then the promise resolves with the result. The screen asks for camera permission the first time it opens. If the person taps Cancel (or Android's back button), the call throws a FacePingError with the code canceled.

const result = await FacePing.verifyLive(ticketId, {
  title: 'Look at the camera to check in',
  cancelLabel: 'Use my ticket instead',
});
camera option Type Default Notes
title string "Enrol your face" (enroll), "Verify your face" (verify, verifyLive) The line at the top of the screen.
captureLabel string "Take photo" The capture button, for enroll and verify.
cancelLabel string "Cancel" The cancel button.
resultDelayMs number 800 How long the result stays on screen before it closes, in milliseconds (0 to 10,000).

Only one camera screen can be open at a time: a second call while one is open throws invalidOperation.

To translate or reword the liveness prompts, use the native SDKs' strings: on Android the string resources faceping_prompt_look_at_camera, faceping_prompt_turn_left, faceping_prompt_turn_right and faceping_prompt_look_back; on iOS a FacePing.strings table with the keys faceping.prompt.lookAtCamera, faceping.prompt.turnLeft, faceping.prompt.turnRight and faceping.prompt.lookBack.

Photos#

A photo is any of:

From Example
Nothing: the camera screen FacePing.enroll('me')
A file path or URI (file://…, or on Android content://…), for example the path from @capacitor/camera FacePing.enroll('me', photo.path) or { uri }
Base64 JPEG or PNG (HEIC too on iOS) { base64 }, or a data:image/jpeg;base64,… URI

Photos are turned upright from their EXIF orientation and scaled down to 1280 pixels on the device before anything else happens.

Backups#

The face store's encryption keys stay in the phone's Keychain or Keystore. On iOS, FacePing's folder is already excluded from backups. On Android, exclude files/faceping and the shared preferences faceping.keys from Auto Backup, or turn backup off (android:allowBackup="false" in android/app/src/main/AndroidManifest.xml).

Results#

type EnrollResult = {
  outcome: 'enrolled' | 'noFace' | 'multipleFaces' | 'faceTooSmall' | 'notLive' | 'limitReached';
  id: string;
  isEnrolled: boolean;
};

type VerifyResult = {
  outcome: 'match' | 'noMatch' | 'notEnrolled' | 'noFace' | 'livenessFailed'
         | 'challengeFailed' | 'expired' | 'leaseExpired';
  similarity: number;           // cosine similarity; the match threshold is 0.363
  livenessScore: number | null; // passive liveness of the deciding frame
  elapsedMs: number;            // how long the check took on the device
  isSandbox: boolean;
  isMatch: boolean;
};

type SyncResult = {
  status: 'ok' | 'gone' | 'revoked' | 'unavailable';
  enrolled: number;
  leaseUntil: Date | null;      // production: verifies offline until then
  deleteAfter: Date | null;
};

The outcome types are exported as EnrollOutcome and VerifyOutcome. limitReached means the sandbox already holds 25 faces on this device: forget one first. leaseExpired (production) means the device hasn't synced for longer than its lease, 24 hours by default; it needs a connection before it verifies again.

Errors#

Set-up and device problems throw a FacePingError with a message ready to show and a code. isFacePingError(e, 'canceled') checks for one.

Code When
setup The key or token is missing, invalid or revoked, the sandbox key couldn't be activated (the first run needs a connection), or init hasn't been called.
model The face models couldn't load on this device. Send people to your normal way in.
invalidOperation Not allowed in this mode or state, for example enrolling on the device in production, or a second camera screen.
invalidArgument A bad argument: an empty id, or a photo that isn't a readable image.
storage The device's secure storage failed.
camera The camera couldn't start (permission refused, no front camera) or didn't deliver a picture.
canceled The person closed the camera screen before the check finished.
unavailable Not running in the native app: a web browser, or the app wasn't synced (npx cap sync) and rebuilt after installing the plugin.
unknown Anything else. The message says what.

Everything else is a result, not an exception: a face check never throws because someone looked away.