Link Identity Platform · v1
API Reference
The Link Identity API exposes device registration, biometric enrollment, verification, and real-time event delivery over a single REST surface. All protected endpoints use OAuth 2.0 Client Credentials and require a Bearer token.
https://link-identity-sandbox-427874453693.me-central1.run.appOverview#
Four steps take you from credentials to a verified palm scan.
- Create OAuth client credentials in Integrations → OAuth Clients.
- Request a token from POST /v1/auth/token.
- Send Authorization: Bearer <access_token> on protected endpoints.
- Refresh the token before expiry using the expires_in field — request a new one ~60 seconds early.
Note
{ success: false, message } — see Error Format for the full shape.Authentication#
Exchange client credentials for a short-lived Bearer token, then attach it to every protected call.
/v1/auth/tokenPublicGenerate an access token. This endpoint itself is public — Bearer auth is not required to call it.
Note
POST https://link-identity-sandbox-427874453693.me-central1.run.app/v1/auth/token
Content-Type: application/json
{
"grant_type": "client_credentials",
"client_id": "06a4dc18-b06c-4e1c-ad25-204d44bf0c71",
"client_secret": "cs_live_xxx"
}{
"success": true,
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"token_type": "Bearer",
"expires_in": 3600
}Credential Rotation#
How rotation and revocation affect tokens already in flight.
Warning
Endpoints
Register Device#
Create a scanner record and receive a one-time pairing code. The hardware_id is assigned by the physical device during pairing — not returned here.
/v1/devicesBearer requiredRegisters a new scanner in the console and returns a pairing code. Enter that code on the physical scanner to complete provisioning.
{
"device_name": "Scanner-04",
"location": "Main Entrance"
}{
"success": true,
"data": {
"device_id": "cced6e19-7e3b-4013-bc04-8bd2efc57e22",
"device_name": "Scanner-04",
"location": "Main Entrance",
"created_at": "2026-06-08T14:08:43.151246Z",
"pairing_code": "mdxglt2xg",
"expires_in": 1800
},
"message": "Enter this pairing code on the physical scanner to complete provisioning."
}Create Both Hands Enrollment Challenge#
Initiate a both-hands biometric enrollment for a user against a specific paired scanner.
/v1/enroll/bothBearer requiredCreates a both-hands enrollment challenge. The device captures both palms; the result is delivered asynchronously via webhook.
Tip
palm_type: both. The completion result is delivered asynchronously via the enrollment.complete webhook.{
"external_user_id": "{{external_user_id}}",
"hardware_id": "hardware_local_002",
"metadata": { "employee_id": "EMP-4421", "department": "Engineering" }
}{
"success": true,
"data": {
"challenge_id": "c4a5dbe7-ae49-4c28-9cdb-9f4558156160",
"type": "enroll",
"external_user_id": "{{external_user_id}}",
"hardware_id": "hardware_local_002",
"palm_type": "both",
"status": "pending",
"expires_at": "2026-06-08T15:08:43.151246Z",
"metadata": { "employee_id": "EMP-4421", "department": "Engineering" }
},
"message": "Enrollment challenge created."
}Create Verification Challenge#
Run a 1:1 palm match against a previously enrolled user.
/v1/verifyBearer requiredCreates a verification challenge. The match result is emitted asynchronously over a webhook.
Tip
verification.complete webhook with a matched boolean and score details.{
"external_user_id": "{{external_user_id}}",
"hardware_id": "{{hardware_id}}",
"metadata": {
"document_id": "doc_123",
"document_hash": "sha256"
}
}Webhooks
Create Webhook#
Register an HTTPS endpoint to receive event deliveries.
/v1/webhooksBearer requiredSubscribes a callback URL to one or more event keys. Deliveries POST a JSON payload signed with the optional API key.
{
"name": "Production Events",
"events": ["device.paired", "enrollment.complete", "verification.complete"],
"endpoint": "https://yourapp.example.com/webhooks/identity",
"api_key": "whsec_your_secret_key_here"
}Event Types#
The three event keys you can subscribe to today.
device.pairedDevice pairing completed.enrollment.completeBiometric enrollment completed.verification.completeVerification match result with scores and thresholds.Payload Format#
Every delivery is a POST with standard headers and a JSON body using event_type, tenant_id, timestamp, and a payload-specific data block.
POST https://yourapp.example.com/webhooks/identity
Content-Type: application/json
X-Link-Event: verification.complete
X-Link-Delivery: del_a1b2c3d4-e5f6-7890-abcd-ef1234567890
User-Agent: LinkIdentity-Webhook/1.0
X-API-Key: whsec_your_secret_key_here{
"event_id": "evt_a1b2c3d4e5f6789012345678abcdef01",
"event_type": "device.paired",
"tenant_id": "d1e2f3a4-b5c6-7890-abcd-ef1234567890",
"timestamp": "2026-06-08T14:32:11.123456Z",
"data": {
"hardware_id": "hardware_local_002",
"device_name": "Lobby Scanner",
"location": "Lobby",
"status": "paired",
"paired_at": "2026-06-08T14:32:10.987654Z",
"cert_valid_until": "2026-09-06T14:32:10.987654Z"
}
}{
"event_id": "evt_b2c3d4e5f6789012345678abcdef012345",
"event_type": "enrollment.complete",
"tenant_id": "d1e2f3a4-b5c6-7890-abcd-ef1234567890",
"timestamp": "2026-06-08T14:35:22.456789Z",
"data": {
"user_id": "7b9957e2-c85f-4f9e-abe1-8aca9b6de70d",
"feature_id": "fid-fixture-001",
"palm_type": "right",
"hardware_id": "hardware_local_002",
"challenge_id": "c4a5dbe7-ae49-4c28-9cdb-9f4558156160",
"enrolled_at": "2026-06-08T14:35:21.912955Z",
"vendor": "mock",
"metadata": {
"source": "mobile-app"
}
}
}{
"event_id": "evt_c3d4e5f6789012345678abcdef01234567",
"event_type": "verification.complete",
"tenant_id": "d1e2f3a4-b5c6-7890-abcd-ef1234567890",
"timestamp": "2026-06-08T14:40:33.789012Z",
"data": {
"challenge_id": "ea1d1e69-f63f-401d-a5b6-6429534bff60",
"user_id": "7b9957e2-c85f-4f9e-abe1-8aca9b6de70d",
"matched": true,
"scores": [0.99, 0.98, 0.97, 0.96, 0.95, 0.94, 0.93, 0.92, 0.91, 0.90],
"thresholds": [0.7072, 0.7253, 0.7018, 0.7211, 0.7426, 0.6929, 0.6850, 0.7100, 0.7526, 0.7004],
"match_policy": "all_thresholds",
"latency_ms": 180,
"vendor": "mock",
"verified_at": "2026-06-08T14:40:32.500000Z",
"metadata": {
"session_id": "sess_0099",
"access_point": "turnstile-7"
}
}
}Note
Reference
Error Format#
Every non-2xx response uses the same JSON shape.
{
"success": false,
"message": "Human-readable description of the error"
}HTTP Status Codes#
The status codes you should expect across the API.