This is the complete API of @fingerly/web-js. For a guided introduction, start with the JavaScript SDK.
import { load } from '@fingerly/web-js'
const fingerly = await load({ apiKey: 'fly_pk_us_production_…' })
const result = await fingerly.identify({ tag: 'login' })
Exports
| Export | Kind | Description |
|---|---|---|
load | Function | Creates a client. |
FingerlyClient | Class | The client load returns. |
TransportError | Class | The error identify rejects with. |
IdentifyOptions, IdentifyResult, IdentifyTrigger, Verdict, Verdicts, Confidence | Types | Described below. |
The script-tag build, dist/fingerly.global.js, exposes the same members on window.Fingerly.
load()
function load(options: FingerlyOptions): Promise<FingerlyClient>
Creates a client. Loading collects and sends nothing. A configuration mistake throws synchronously, so call it with await inside try.
Load options
apiKeystringrequiredYour public key. Its prefix decides which regional API is used.endpointsstring | string[]Ordered base URLs or root-relative paths to send requests to instead of the regional API, for a proxy./api/v1/identifyis appended to each. They are tried in order when one fails.endpointstringA single base URL or path. Preferendpoints. Passing both throws.fallbackToDefaultEndpointbooleanDefaultfalseTry the key's regional API after the custom endpoints fail.consent'granted' | 'pending' | 'denied'Default'granted'Whether the visitor has consented. Until it isgranted,identify()andcollect()collect and send nothing and reject with aConsentError. See consent.transportobjectReplaces how requests are sent, for tests: an object whosesubmit(submission, signal)resolves with an identify response, and optionallysubmitSupplement(requestId, submission, signal).endpoint,endpointsandfallbackToDefaultEndpointare then ignored. See testing.sourcesarrayThe signal sources to run. Every browser source by default; pass[]in tests to collect nothing.scheduleobjectCollection tuning forcollect()and foridentify()with explicittiers:budgetMs,defaultSourceTimeoutMsandconcurrency.3 child fields
budgetMsnumberDefault1200The total collection budget.defaultSourceTimeoutMsnumberDefault300The time any one source may take.concurrencynumberDefault6Sources collected at once.
Where requests go
| Key prefix | API |
|---|---|
fly_pk_us_… | https://us.api.fingerly.io |
fly_pk_eu_… | https://eu.api.fingerly.io (coming soon) |
load throws when apiKey is empty, when both endpoint and endpoints are set, when endpoints is empty, or when an endpoint is neither an absolute http(s) URL nor a path starting with /, or contains a query or fragment.
identify()
fingerly.identify(options?: IdentifyOptions): Promise<IdentifyResult>
Collects signals, submits them and resolves with the server's verdict. Each call is a new identification with its own idempotency key; the SDK does not cache results.
IdentifyOptions
tagstringYour own reference for this identification. Echoed on the event, in webhooks and in the dashboard.submitbooleanDefaulttruefalsecollects and computes local verdicts without sending anything.signalAbortSignalCancels collection and submission.tiersArray<'fast' | 'deferred'>Collect these tiers in one pass underschedule.budgetMs, with no separate deferred report. Omit for the default: a 300 ms initial tier, then a 1,200 ms deferred tier after the answer.
IdentifyResult
requestIdstringThe identification's ID. Empty when not submitted.visitorIdstringThe stable visitor identifier. Empty only when not submitted or on a duplicate.visitorIsNewbooleanWhether your organization is seeing this visitor for the first time.visitorConfidencenumber0 to 100:100for an exact match,85to99when recognised after the device changed,0for a new visitor ID.identifiablebooleanfalsewhen the browser gave too little to identify anyone. Still scored.duplicatebooleantruewhen the server had already answered this request. Keep the first response.statestringenriched, orunavailablewhen the network lookup could not run. Empty when not submitted.suspectScorenumber | nullThe weighted sum of the signals that fired.nullwhen not scored, which is not0. Not a percentage and not capped.suspectLevelstring | nulllow,mediumorhigh.nullexactly whensuspectScoreis.triggersIdentifyTrigger[]The signals that fired, heaviest first.4 child fields
signalstringThe signal.groupstringIts group.weightnumberThe weight it added.confidencestringlow,mediumorhigh.
verdictsVerdictsLocal verdicts from the initial tier. Advisory.reportSignalReportThe report that was sent.deferredPromise<DeferredIdentifyResult>The deferred tier's outcome. Never rejects.
Verdicts
| Key | Matches when |
|---|---|
incognito | The page is in a private browsing window. |
shields | Anti-fingerprinting protections are rewriting values. A privacy choice, not a reason to challenge anyone. |
tor | The browser is Tor Browser. |
emulator | The browser runs in an emulated device. |
automation | The browser is driven by automation. |
farm | The browser looks mass-provisioned or reset fresh. |
Verdict
valuebooleanWhether the verdict matched.confidence'low' | 'medium' | 'high'How strong the evidence is.reasonsstring[]Short, stable tokens for the evidence, strongest first.
DeferredIdentifyResult
| `status` | Fields | When |
|---|---|---|
'submitted' | report, combinedReport, verdicts | The deferred report was sent. verdicts covers both tiers. |
'skipped' | reason: 'not-submitted', 'explicit-tiers', 'duplicate' | No deferred report applied. |
'failed' | error, and whatever was collected | Collection or sending failed. The identification itself is unaffected. |
collect()
fingerly.collect(options?: IdentifyOptions): Promise<SignalReport>
Runs collection and returns the report without submitting anything. Uses tiers (both by default), signal and schedule.budgetMs.
setConsent()
fingerly.setConsent(state: 'granted' | 'pending' | 'denied'): void
fingerly.consent: 'granted' | 'pending' | 'denied'
fingerly.onConsentChange(listener: (state) => void): () => void
Changes the consent state. It applies to the next call and to any call already running: withdrawing consent stops collection and sending, and a running deferred tier resolves as skipped with the reason consent-withdrawn. onConsentChange calls its listener on every change and returns a function that unsubscribes. Any other value throws a TypeError.
ConsentError
declare class ConsentError extends Error {
readonly name: 'ConsentError'
readonly code: 'consent_required'
readonly state: 'pending' | 'denied'
}
identify() and collect() reject with it when consent is not granted, or is withdrawn while they run.
TransportError
declare class TransportError extends Error {
readonly name: 'TransportError'
readonly status?: number // the HTTP status, when the server answered
readonly retryable: boolean // whether trying again later may succeed
}
| Situation | `status` | `retryable` |
|---|---|---|
| The server refused the request | The HTTP status | true for 404, 405, 408, 425, 429 and 5xx |
| The network failed or an attempt timed out | none | true |
| The request was aborted | none | false |
A 2xx without a request ID, such as a captive portal page | none | false |
identify rejects only after its own retries: up to three attempts, 5 seconds each, with jittered backoff and one idempotency key.
Runtime
- ES2020, using
fetchandAbortController. No workers, WebAssembly orevalare required. - Requests omit credentials and set no cookies. The SDK stores no identifier on the device.
- About 18 KB gzipped for the script-tag build.