# Avalonia reference

> Every public type in FacePing.Sdk.Avalonia: setup, enrolment, verification, the camera control and results.

Source: https://docs.faceping.ai/sdk/avalonia/

Package: [`FacePing.Sdk.Avalonia`](https://www.nuget.org/packages/FacePing.Sdk.Avalonia) (NuGet, preview). Starter app: [faceping/avalonia-starter](https://github.com/faceping/avalonia-starter). Avalonia 12 and .NET 10. Android 7 (API 24) or later on 64-bit phones, iOS 15 or later.

Namespaces: `FacePing.Sdk.Avalonia` for setup, `IFacePing`, `FaceImage` and `FaceCameraView`; `FacePing.Sdk` for results and exceptions, the same on every platform. With implicit usings on, the package adds `using FacePing.Sdk` for you.

## Install

Add the package to your shared project (your views) and to the Android and iOS projects:

```bash
dotnet add FaceCheck package FacePing.Sdk.Avalonia --prerelease
dotnet add FaceCheck.Android package FacePing.Sdk.Avalonia --prerelease
dotnet add FaceCheck.iOS package FacePing.Sdk.Avalonia --prerelease
```

The shared project compiles against the same API, and each platform project brings the real thing: the native core, the face models and the camera. On desktop and in the browser your app still builds, but face checks throw `FacePingModelException`: FacePing runs on Android and iOS devices.

The Android project needs `<RuntimeIdentifiers>android-arm64;android-x64</RuntimeIdentifiers>` (FacePing runs on 64-bit phones and x86_64 emulators) and `<SupportedOSPlatformVersion>24</SupportedOSPlatformVersion>`. The iOS project needs `<SupportedOSPlatformVersion>15.0</SupportedOSPlatformVersion>`.

## Setup

In `CustomizeAppBuilder`, in Android's `Application` and in iOS's `AppDelegate`:

```csharp
builder.UseFacePing(o =>
{
    o.SandboxKey = "fp_test_…";                 // sandbox: faces enrolled on the device
    // o.DeviceToken = "fpd_…";                 // production: faces synced from your list
    // o.Region = FacePingRegion.EU;          // your account's region (the default)
});
```

### FacePingOptions

| Property | Type | Default | Notes |
|---|---|---|---|
| `SandboxKey` | `string?` | `null` | A `fp_test_` key from [**Developers → Sandbox keys**](https://app.faceping.ai/developers). |
| `DeviceToken` | `string?` | `null` | Production. A `fpd_` token for one check-in list. Set this or `SandboxKey`, not both. |
| `Region` | `FacePingRegion` | `EU` | Your account's region, which sets the API address. Today the only value is EU (`https://api-eu.faceping.ai/`). |
| `ApiBaseUrl` | `Uri?` | `null` | Overrides the region's address. UK and US regions are available on request: we give you the address to put here. |
| `PassiveLiveness` | `bool` | `true` | Passive liveness on every check. Turn it off only if your app runs its own liveness check first. |
| `AutoSyncInterval` | `TimeSpan?` | 5 minutes | Production only. `null` turns automatic sync off; call `SyncAsync` yourself. |
| `VerifyPolicy` | `VerifyPolicy` | `VerifyPolicy.Default` | How strict verification is. Change only if you know why. |

`UseFacePing` sets FacePing up once and starts loading the face models in the background. Then use `IFacePing.Current` anywhere, or register it in your own container.

Setting neither `SandboxKey` nor `DeviceToken`, or both, throws `FacePingSetupException` when the app starts.

## IFacePing

```csharp
public interface IFacePing
{
    static IFacePing Current { get; }   // the app's FacePing, once UseFacePing has run

    bool IsSandbox { get; }
    IReadOnlyCollection<string> EnrolledIds { get; }

    Task<EnrollResult> EnrollAsync(string id, FaceImage photo, CancellationToken ct = default);
    Task<VerifyResult> VerifyLiveAsync(string id, FaceCameraView camera, CancellationToken ct = default);
    Task<VerifyResult> VerifyAsync(string id, FaceImage photo, CancellationToken ct = default);

    Task<bool> ForgetAsync(string id);
    Task ForgetAllAsync();

    Task<SyncResult> SyncAsync(CancellationToken ct = default);   // sandbox: activate the key; production: fetch faces
    event EventHandler<SyncResult>? Synced;                         // production
}
```

| Method | Notes |
|---|---|
| `EnrollAsync` | Sandbox only. Production enrolment happens with consent on your server or through the FacePing widget. Replaces any face already stored under `id`. |
| `VerifyLiveAsync` | Runs a live attempt on the camera's frames: match, head turn, match again. Shows prompts in the `FaceCameraView`. Times out after 10 seconds with `NoFace` or the last failure. |
| `VerifyAsync` | One photo, passive liveness only. |
| `ForgetAsync` | Sandbox: deletes the face stored under `id` from the device straight away. Production: throws `InvalidOperationException`; remove a person through your server instead, and every device drops them at its next sync. |
| `ForgetAllAsync` | Deletes every face on the device straight away. In production the next sync brings back the people still enrolled. |
| `EnrolledIds` | Who can be verified on this device, as of the last enrol, forget or sync. |
| `SyncAsync` | Production: downloads the latest faces for the device's 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. |

## FaceImage

What you can pass as a photo. Bytes and streams convert implicitly; use `FaceImage.FromFile` for a storage file.

| From | Example |
|---|---|
| `byte[]` (JPEG or PNG) | `await faceping.EnrollAsync("me", bytes)` |
| `Stream` | `await faceping.EnrollAsync("me", stream)` |
| `IStorageFile` (from a file picker) | `await faceping.EnrollAsync("me", FaceImage.FromFile(file))` |
| `FaceCameraView` capture | `await faceping.EnrollAsync("me", await Camera.CaptureAsync())` |

Large photos are scaled down to 1280 pixels on the device before anything else happens.

## FaceCameraView

A front-camera control with a face guide and the liveness prompts ("Turn your head left", "Look back at the camera"). FacePing runs the camera itself (CameraX on Android, AVFoundation on iOS) and draws the picture in Avalonia, so the control sits in your layout like any other: rounded corners, clipping and overlays all work.

```xml
<!-- xmlns:fp="using:FacePing.Sdk.Avalonia" -->
<fp:FaceCameraView x:Name="Camera" Height="420" />
```

| Member | Notes |
|---|---|
| `Task<FaceImage> CaptureAsync()` | Takes a still from the live view: upright, as the camera sees it (the picture on screen is mirrored, this isn't). |
| `bool ShowGuide` | The face outline. Default `true`. |
| `string? Prompt` | Set by `VerifyLiveAsync`; bind to it if you draw your own prompts. |
| `FaceGuideState GuideState` | How the guide looks: `Ready` (dashed), `Working` (a scan line), `Match` (green) or `NoMatch` (red). `VerifyLiveAsync` sets it; set it yourself for other steps, for example `Working` while enrolling. |
| `event CameraFailed` | The camera couldn't start: permission refused or no front camera. The message says which. Raised on the UI thread. |
| `void StartCamera()` | Starts the camera again, for example after the person allows it in Settings. Called for you when the control appears. |

The camera starts when the control appears and stops when it leaves the screen. Camera permission is requested the first time: the package declares Android's camera permission, and your iOS project's Info.plist needs `NSCameraUsageDescription`.

## Results

```csharp
public record EnrollResult(EnrollOutcome Outcome, string Id)
{
    public bool IsEnrolled { get; }   // Outcome == Enrolled
}

public enum EnrollOutcome
{
    Enrolled, NoFace, MultipleFaces, FaceTooSmall, NotLive, LimitReached
}

public record VerifyResult(
    VerifyOutcome Outcome,
    double Similarity,
    double? LivenessScore,
    TimeSpan Elapsed,
    bool IsSandbox)
{
    public bool IsMatch { get; }   // Outcome == Match
}

public enum VerifyOutcome
{
    Match, NoMatch, NotEnrolled, NoFace, LivenessFailed, ChallengeFailed, Expired, LeaseExpired
}

public record SyncResult(
    SyncStatus Status,                  // Ok, Gone, Revoked or Unavailable
    int Enrolled,
    DateTimeOffset? LeaseUntil,         // production: verifies offline until then
    DateTimeOffset? DeleteAfter);
```

`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.

## EnrollmentPhoto

For [server-side enrolment](https://docs.faceping.ai/server-enrollment/): shrinks a camera photo before your app uploads it to your server. FacePing works at 1280 px, so a 12 MP original only costs upload time; this makes about 1.4 MB into 0.2 to 0.3 MB with the same result, and applies the photo's EXIF orientation. It runs in the FacePing core, with the same decoder as the server.

```csharp
public static class EnrollmentPhoto            // namespace FacePing.Sdk
{
    public const int DefaultQuality = 90;

    // A JPEG, upright, longest side at most 1280 px. Throws ArgumentException if the photo isn't a JPEG or PNG.
    public static byte[] Prepare(byte[] photo, int quality = DefaultQuality);
}
```

## Exceptions

| Exception | When |
|---|---|
| `FacePingSetupException` | The key or token is missing, invalid or revoked, or the sandbox key couldn't be activated (no connection on first run). The message says which. |
| `FacePingModelException` | The face models couldn't load on this device, or the app is running on desktop or in the browser. Send people to your normal entry. |

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