diff --git a/.changeset/config.json b/.changeset/config.json index aabcbd6e..70dd986c 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -3,7 +3,7 @@ "changelog": [ "@fingerprintjs/changesets-changelog-format", { - "repo": "fingerprintjs/fingerprintjs-pro-server-api-node-sdk" + "repo": "fingerprintjs/node-sdk" } ], "commit": false, @@ -12,5 +12,5 @@ "access": "public", "baseBranch": "main", "updateInternalDependencies": "patch", - "ignore": ["fingerprintjs-pro-server-api-node-sdk-example"] + "ignore": ["fingerprint-server-sdk-example", "fingerprint-server-sdk-smoke-tests"] } diff --git a/.changeset/early-seas-look.md b/.changeset/early-seas-look.md new file mode 100644 index 00000000..4fde6704 --- /dev/null +++ b/.changeset/early-seas-look.md @@ -0,0 +1,22 @@ +--- +'@fingerprint/fingerprint-server-sdk': major +--- + +**Server APIv3 -> Server APIv4 migration** + +- Switch all endpoints to `/v4/*`. +- Remove `authenticationMode` option when initializing `FingerprintServerApiClient`. +- Rename `request_id` to `event_id`. +- Use snake_case fields when updating an event. +- Use `PATCH` method when updating an event. +- Examples, tests, and docs updated. + +**BREAKING CHANGES** +- Use new client when initializing: `FingerprintServerApiClient`. +- `authenticationMode` option removed. +- Removed `getVisits()` function. +- Removed `getRelatedVisitors()` function. +- Removed `VisitorHistoryFilter`, `ErrorPlainResponse`, `VisitorsResponse`, `RelatedVisitorsResponse`, +`RelatedVisitorsFilter`, `Webhook`, `EventsUpdateRequest` types. +- Use `tags` instead of `tag` for updating an event. +- Response models changed. diff --git a/.changeset/loud-waves-drive.md b/.changeset/loud-waves-drive.md new file mode 100644 index 00000000..d8c1702f --- /dev/null +++ b/.changeset/loud-waves-drive.md @@ -0,0 +1,5 @@ +--- +'@fingerprint/fingerprint-server-sdk': major +--- + +change package name to `@fingerprint/fingerprint-server-sdk` diff --git a/.changeset/pre.json b/.changeset/pre.json new file mode 100644 index 00000000..2f9c0c51 --- /dev/null +++ b/.changeset/pre.json @@ -0,0 +1,10 @@ +{ + "mode": "pre", + "tag": "test", + "initialVersions": { + "@fingerprint/fingerprint-server-sdk": "6.10.0", + "fingerprint-server-sdk-example": "1.0.0", + "fingerprint-server-sdk-smoke-tests": "2.0.0" + }, + "changesets": [] +} diff --git a/.changeset/witty-lobsters-film.md b/.changeset/witty-lobsters-film.md new file mode 100644 index 00000000..73e90628 --- /dev/null +++ b/.changeset/witty-lobsters-film.md @@ -0,0 +1,5 @@ +--- +'@fingerprint/fingerprint-server-sdk': minor +--- + +add `options` parameter to the `getEvent` operation diff --git a/.schema-version b/.schema-version index a5db00c8..b105cea1 100644 --- a/.schema-version +++ b/.schema-version @@ -1 +1 @@ -v2.12.0 \ No newline at end of file +v3.0.1 diff --git a/contributing.md b/contributing.md index bc7a70ac..3c3459b8 100644 --- a/contributing.md +++ b/contributing.md @@ -84,19 +84,11 @@ Use the `example` folder to make API requests using the local version of the SDK cd example pnpm install node getEvent.mjs - node getVisitorHistory.mjs + node searchEvents.mjs ``` Every time you change the SDK code, you need to rebuild it in the root folder using `pnpm build` and then run the example again. ### How to publish -The library is automatically released and published to NPM on every push to the main branch if there are relevant changes using [semantic-release](https://github.com/semantic-release/semantic-release) with following plugins: - -- [@semantic-release/commit-analyzer](https://github.com/semantic-release/commit-analyzer) -- [@semantic-release/release-notes-generator](https://github.com/semantic-release/release-notes-generator) -- [@semantic-release/changelog](https://github.com/semantic-release/changelog) -- [@semantic-release/npm](https://github.com/semantic-release/npm) -- [@semantic-release/github](https://github.com/semantic-release/github) - -The workflow must be approved by one of the maintainers, first. +We use [changesets](https://github.com/changesets/changesets) for handling release notes. If there are relevant changes, please add them to changeset via `pnpm exec changeset`. You need to run `pnpm install` before doing so. diff --git a/example/.env.example b/example/.env.example index de62c8fe..4f806c18 100644 --- a/example/.env.example +++ b/example/.env.example @@ -1,8 +1,9 @@ API_KEY= VISITOR_ID= -REQUEST_ID= +EVENT_ID= +RULESET_ID= # "eu" or "ap", "us" is the default REGION= WEBHOOK_SIGNATURE_SECRET= BASE64_KEY= -BASE64_SEALED_RESULT= \ No newline at end of file +BASE64_SEALED_RESULT= diff --git a/example/deleteVisitor.mjs b/example/deleteVisitor.mjs index ee0ae670..27038522 100644 --- a/example/deleteVisitor.mjs +++ b/example/deleteVisitor.mjs @@ -1,4 +1,4 @@ -import { FingerprintJsServerApiClient, Region, RequestError } from '@fingerprintjs/fingerprintjs-pro-server-api' +import { FingerprintServerApiClient, Region, RequestError } from '@fingerprint/fingerprint-server-sdk' import { config } from 'dotenv' config() @@ -23,7 +23,7 @@ if (envRegion === 'eu') { region = Region.AP } -const client = new FingerprintJsServerApiClient({ region, apiKey }) +const client = new FingerprintServerApiClient({ region, apiKey }) try { await client.deleteVisitorData(visitorId) diff --git a/example/getEvent.mjs b/example/getEvent.mjs index 6c2a07f9..e529cb94 100644 --- a/example/getEvent.mjs +++ b/example/getEvent.mjs @@ -1,13 +1,14 @@ -import { FingerprintJsServerApiClient, Region, RequestError } from '@fingerprintjs/fingerprintjs-pro-server-api' +import { FingerprintServerApiClient, Region, RequestError } from '@fingerprint/fingerprint-server-sdk' import { config } from 'dotenv' config() const apiKey = process.env.API_KEY -const requestId = process.env.REQUEST_ID +const eventId = process.env.EVENT_ID +const rulesetId = process.env.RULESET_ID const envRegion = process.env.REGION -if (!requestId) { - console.error('Request ID not defined') +if (!eventId) { + console.error('Event ID not defined') process.exit(1) } @@ -23,11 +24,20 @@ if (envRegion === 'eu') { region = Region.AP } -const client = new FingerprintJsServerApiClient({ region, apiKey }) +const client = new FingerprintServerApiClient({ region, apiKey }) try { - const event = await client.getEvent(requestId) + const event = await client.getEvent(eventId, { ruleset_id: rulesetId }) console.log(JSON.stringify(event, null, 2)) + + if (rulesetId && event.rule_action) { + const { type, ruleset_id, rule_id, rule_expression } = event.rule_action + console.log(`Rule action: ${type} (ruleset: ${ruleset_id}, rule: ${rule_id}, expression: ${rule_expression})`) + + if (type === 'block') { + console.log(`Block response: ${event.rule_action.status_code} ${event.rule_action.body}`) + } + } } catch (error) { if (error instanceof RequestError) { console.log(`error ${error.statusCode}: `, error.message) diff --git a/example/getVisitorHistory.mjs b/example/getVisitorHistory.mjs deleted file mode 100644 index 4d70f43c..00000000 --- a/example/getVisitorHistory.mjs +++ /dev/null @@ -1,53 +0,0 @@ -import { - FingerprintJsServerApiClient, - Region, - RequestError, - TooManyRequestsError, -} from '@fingerprintjs/fingerprintjs-pro-server-api' -import { config } from 'dotenv' -config() - -const apiKey = process.env.API_KEY -const visitorId = process.env.VISITOR_ID -const envRegion = process.env.REGION - -if (!visitorId) { - console.error('Visitor ID not defined') - process.exit(1) -} - -if (!apiKey) { - console.error('API key not defined') - process.exit(1) -} - -let region = Region.Global -if (envRegion === 'eu') { - region = Region.EU -} else if (envRegion === 'ap') { - region = Region.AP -} - -const client = new FingerprintJsServerApiClient({ region, apiKey }) - -try { - const visitorHistory = await client.getVisits(visitorId, { limit: 10 }) - console.log(JSON.stringify(visitorHistory, null, 2)) -} catch (error) { - if (error instanceof RequestError) { - console.log(error.statusCode, error.message) - if (error instanceof TooManyRequestsError) { - retryLater(error.retryAfter) // Needs to be implemented on your side - } - } else { - console.error('unknown error: ', error) - } - process.exit(1) -} - -/** - * @param {number} delay - How many seconds to wait before retrying - */ -function retryLater(delay) { - console.log(`Implement your own retry logic here and retry after ${delay} seconds`) -} diff --git a/example/package.json b/example/package.json index aab84cee..6db4576a 100644 --- a/example/package.json +++ b/example/package.json @@ -1,5 +1,5 @@ { - "name": "fingerprintjs-pro-server-api-node-sdk-example", + "name": "fingerprint-server-sdk-example", "version": "1.0.0", "description": "", "main": "index.mjs", @@ -10,7 +10,7 @@ "author": "", "license": "MIT", "dependencies": { - "@fingerprintjs/fingerprintjs-pro-server-api": "workspace:*", + "@fingerprint/fingerprint-server-sdk": "workspace:*", "dotenv": "^16.4.5" } } diff --git a/example/relatedVisitors.mjs b/example/relatedVisitors.mjs deleted file mode 100644 index 6e202de7..00000000 --- a/example/relatedVisitors.mjs +++ /dev/null @@ -1,43 +0,0 @@ -import { FingerprintJsServerApiClient, Region, RequestError } from '@fingerprintjs/fingerprintjs-pro-server-api' -import { config } from 'dotenv' -config() - -const apiKey = process.env.API_KEY -const visitorId = process.env.VISITOR_ID -const envRegion = process.env.REGION - -if (!visitorId) { - console.error('Visitor ID not defined') - process.exit(1) -} - -if (!apiKey) { - console.error('API key not defined') - process.exit(1) -} - -let region = Region.Global -if (envRegion === 'eu') { - region = Region.EU -} else if (envRegion === 'ap') { - region = Region.AP -} - -const client = new FingerprintJsServerApiClient({ region, apiKey }) - -try { - const relatedVisitors = await client.getRelatedVisitors({ - visitor_id: visitorId, - }) - - console.log(JSON.stringify(relatedVisitors, null, 2)) -} catch (error) { - if (error instanceof RequestError) { - console.log(`error ${error.statusCode}: `, error.message) - // You can also access the raw response - console.log(error.response.statusText) - } else { - console.log('unknown error: ', error) - } - process.exit(1) -} diff --git a/example/searchEvents.mjs b/example/searchEvents.mjs index eaf8a0ad..d71362f1 100644 --- a/example/searchEvents.mjs +++ b/example/searchEvents.mjs @@ -1,4 +1,4 @@ -import { FingerprintJsServerApiClient, Region, RequestError } from '@fingerprintjs/fingerprintjs-pro-server-api' +import { FingerprintServerApiClient, Region, RequestError } from '@fingerprint/fingerprint-server-sdk' import { config } from 'dotenv' config() @@ -17,7 +17,7 @@ if (envRegion === 'eu') { region = Region.AP } -const client = new FingerprintJsServerApiClient({ region, apiKey }) +const client = new FingerprintServerApiClient({ region, apiKey }) const filter = { limit: 10, @@ -25,7 +25,7 @@ const filter = { // bot: 'all', // visitor_id: 'TaDnMBz9XCpZNuSzFUqP', // ip_address: '192.168.0.1/32', - // linked_id: ', + // linked_id: '', //start: 1620000000000, //end: 1630000000000, //reverse: true, diff --git a/example/unsealResult.mjs b/example/unsealResult.mjs index 2bc8e895..6d6c3b1b 100644 --- a/example/unsealResult.mjs +++ b/example/unsealResult.mjs @@ -1,4 +1,4 @@ -import { unsealEventsResponse, DecryptionAlgorithm } from '@fingerprintjs/fingerprintjs-pro-server-api' +import { unsealEventsResponse, DecryptionAlgorithm } from '@fingerprint/fingerprint-server-sdk' import { config } from 'dotenv' config() diff --git a/example/updateEvent.mjs b/example/updateEvent.mjs index 98083a04..8bd0f7d9 100644 --- a/example/updateEvent.mjs +++ b/example/updateEvent.mjs @@ -1,14 +1,14 @@ -import { FingerprintJsServerApiClient, RequestError, Region } from '@fingerprintjs/fingerprintjs-pro-server-api' +import { FingerprintServerApiClient, RequestError, Region } from '@fingerprint/fingerprint-server-sdk' import { config } from 'dotenv' config() const apiKey = process.env.API_KEY -const requestId = process.env.REQUEST_ID +const eventId = process.env.EVENT_ID const envRegion = process.env.REGION -if (!requestId) { - console.error('Request ID not defined') +if (!eventId) { + console.error('Event ID not defined') process.exit(1) } @@ -24,18 +24,18 @@ if (envRegion === 'eu') { region = Region.AP } -const client = new FingerprintJsServerApiClient({ region, apiKey }) +const client = new FingerprintServerApiClient({ region, apiKey }) try { await client.updateEvent( { - tag: { + tags: { key: 'value', }, - linkedId: 'new_linked_id', + linked_id: 'new_linked_id', suspect: false, }, - requestId + eventId ) console.log('Event updated') diff --git a/example/validateWebhookSignature.mjs b/example/validateWebhookSignature.mjs index 9b2b1a72..d1b5ef93 100644 --- a/example/validateWebhookSignature.mjs +++ b/example/validateWebhookSignature.mjs @@ -1,4 +1,4 @@ -import { isValidWebhookSignature } from '@fingerprintjs/fingerprintjs-pro-server-api' +import { isValidWebhookSignature } from '@fingerprint/fingerprint-server-sdk' /** * Webhook endpoint handler example diff --git a/package.json b/package.json index dd2bcfd3..38ab5e73 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { - "name": "@fingerprintjs/fingerprintjs-pro-server-api", + "name": "@fingerprint/fingerprint-server-sdk", "version": "6.10.0", - "description": "Node.js wrapper for FingerprintJS Sever API", + "description": "Node.js wrapper for Fingerprint Server API", "main": "dist/index.cjs", "module": "dist/index.mjs", "types": "dist/index.d.ts", @@ -15,7 +15,7 @@ }, "repository": { "type": "git", - "url": "https://github.com/fingerprintjs/fingerprintjs-pro-server-api-node-sdk" + "url": "https://github.com/fingerprintjs/node-sdk" }, "engines": { "node": ">=18.17.0" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 548bd267..abc60c41 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -98,7 +98,7 @@ importers: example: dependencies: - '@fingerprintjs/fingerprintjs-pro-server-api': + '@fingerprint/fingerprint-server-sdk': specifier: workspace:* version: link:.. dotenv: @@ -107,7 +107,7 @@ importers: tests/functional-tests: dependencies: - '@fingerprintjs/fingerprintjs-pro-server-api': + '@fingerprint/fingerprint-server-sdk': specifier: workspace:* version: link:../.. dotenv: diff --git a/readme.md b/readme.md index 0511d060..3c32caa9 100644 --- a/readme.md +++ b/readme.md @@ -8,14 +8,14 @@

- Build status - coverage - Current NPM version - Monthly downloads from NPM + Build status + coverage + Current NPM version + Monthly downloads from NPM Discord server

-# Fingerprint Server API Node.js SDK +# Fingerprint Server Node.js SDK [Fingerprint](https://fingerprint.com) is a device intelligence platform offering industry-leading accuracy. @@ -39,7 +39,7 @@ Supported runtimes: To make it work, replace the SDK's built-in `fetch` function (which relies on Node APIs) with the runtime's native `fetch` function. Pass the function into the constructor with proper binding: ```js - const client = new FingerprintJsServerApiClient({ + const client = new FingerprintServerApiClient({ region: Region.EU, apiKey: apiKey, fetch: fetch.bind(globalThis), @@ -55,16 +55,16 @@ Install the package using your favorite package manager: - NPM: ```sh - npm i @fingerprintjs/fingerprintjs-pro-server-api + npm i @fingerprint/fingerprint-server-sdk ``` - Yarn: ```sh - yarn add @fingerprintjs/fingerprintjs-pro-server-api + yarn add @fingerprint/fingerprint-server-sdk ``` - pnpm: ```sh - pnpm i @fingerprintjs/fingerprintjs-pro-server-api + pnpm i @fingerprint/fingerprint-server-sdk ``` ## Getting started @@ -73,30 +73,35 @@ Initialize the client instance and use it to make API requests. You need to spec ```ts import { - FingerprintJsServerApiClient, + FingerprintServerApiClient, Region, -} from '@fingerprintjs/fingerprintjs-pro-server-api' +} from '@fingerprint/fingerprint-server-sdk' -const client = new FingerprintJsServerApiClient({ +const client = new FingerprintServerApiClient({ apiKey: '', region: Region.Global, }) // Get visit history of a specific visitor -client.getVisits('').then((visitorHistory) => { +client.searchEvents({ visitor_id: '' }).then((visitorHistory) => { console.log(visitorHistory) }) // Get a specific identification event -client.getEvent('').then((event) => { +client.getEvent('').then((event) => { console.log(event) }) +// Get an event with a ruleset evaluation +client.getEvent('', { ruleset_id: '' }).then((event) => { + console.log(event.rule_action?.type) // 'allow' or 'block' +}) + // Search for identification events client .searchEvents({ limit: 10, -// pagination_key: previousSearchResult.paginationKey, +// pagination_key: previousSearchResult.pagination_key, suspect: true, }) .then((events) => { @@ -104,7 +109,7 @@ client }) ``` -See the [Examples](https://github.com/fingerprintjs/fingerprintjs-pro-server-api-node-sdk/tree/main/example) folder for more detailed examples. +See the [Examples](https://github.com/fingerprintjs/node-sdk/tree/main/example) folder for more detailed examples. ### Error handling @@ -114,18 +119,18 @@ When handling errors, you can check for it like this: ```typescript import { RequestError, - FingerprintJsServerApiClient, + FingerprintServerApiClient, TooManyRequestsError, -} from '@fingerprintjs/fingerprintjs-pro-server-api' +} from '@fingerprint/fingerprint-server-sdk' -const client = new FingerprintJsServerApiClient({ +const client = new FingerprintServerApiClient({ apiKey: '', region: Region.Global, }) // Handling getEvent errors try { - const event = await client.getEvent(requestId) + const event = await client.getEvent(eventId) console.log(JSON.stringify(event, null, 2)) } catch (error) { if (error instanceof RequestError) { @@ -136,40 +141,18 @@ try { console.log('unknown error: ', error) } } - -// Handling getVisits errors -try { - const visitorHistory = await client.getVisits(visitorId, { - limit: 10, - }) - console.log(JSON.stringify(visitorHistory, null, 2)) -} catch (error) { - if (error instanceof RequestError) { - console.log(error.status, error.error) - if (error instanceof TooManyRequestsError) { - retryLater(error.retryAfter) // Needs to be implemented on your side - } - } else { - console.error('unknown error: ', error) - } - - // You can also check for specific error instance - // if(error instanceof VisitorsError403) { - // Handle 403 error... - // } -} ``` ### Webhooks #### Webhook types -When handling [Webhooks](https://dev.fingerprint.com/docs/webhooks) coming from Fingerprint, you can cast the payload as the built-in `VisitWebhook` type: +When handling [Webhooks](https://dev.fingerprint.com/reference/posteventwebhook#/) coming from Fingerprint, you can cast the payload as the built-in `Event` type: ```ts -import { VisitWebhook } from '@fingerprintjs/fingerprintjs-pro-server-api' +import { Event } from '@fingerprint/fingerprint-server-sdk' -const visit = visitWebhookBody as unknown as VisitWebhook +const event = eventWebhookBody as unknown as Event ``` #### Webhook signature validation @@ -177,21 +160,21 @@ const visit = visitWebhookBody as unknown as VisitWebhook Customers on the Enterprise plan can enable [Webhook signatures](https://dev.fingerprint.com/docs/webhooks-security) to cryptographically verify the authenticity of incoming webhooks. This SDK provides a utility method for verifying the HMAC signature of the incoming webhook request. -To learn more, see [example/validateWebhookSignature.mjs](example/validateWebhookSignature.mjs) or read the [API Reference](https://fingerprintjs.github.io/fingerprintjs-pro-server-api-node-sdk/functions/isValidWebhookSignature.html). +To learn more, see [example/validateWebhookSignature.mjs](example/validateWebhookSignature.mjs) or read the [API Reference](https://fingerprintjs.github.io/node-sdk/functions/isValidWebhookSignature.html). ### Sealed results Customers on the Enterprise plan can enable [Sealed results](https://dev.fingerprint.com/docs/sealed-client-results) to receive the full device intelligence result on the client and unseal it on the server. This SDK provides utility methods for decoding sealed results. -To learn more, see [example/unsealResult.mjs](https://github.com/fingerprintjs/fingerprintjs-pro-server-api-node-sdk/tree/main/example/unsealResult.mjs) or the [API Reference](https://fingerprintjs.github.io/fingerprintjs-pro-server-api-node-sdk/functions/unsealEventsResponse.html). +To learn more, see [example/unsealResult.mjs](https://github.com/fingerprintjs/node-sdk/tree/main/example/unsealResult.mjs) or the [API Reference](https://fingerprintjs.github.io/node-sdk/functions/unsealEventsResponse.html). ### Deleting visitor data -Customers on the Enterprise plan can [Delete all data associated with a specific visitor](https://dev.fingerprint.com/reference/deletevisitordata) to comply with privacy regulations. See [example/deleteVisitor.mjs](https://github.com/fingerprintjs/fingerprintjs-pro-server-api-node-sdk/tree/main/example/deleteVisitor.mjs) or the [API Reference](https://fingerprintjs.github.io/fingerprintjs-pro-server-api-node-sdk/classes/FingerprintJsServerApiClient.html#deleteVisitorData). +Customers on the Enterprise plan can [Delete all data associated with a specific visitor](https://dev.fingerprint.com/reference/deletevisitordata) to comply with privacy regulations. See [example/deleteVisitor.mjs](https://github.com/fingerprintjs/node-sdk/tree/main/example/deleteVisitor.mjs) or the [API Reference](https://fingerprintjs.github.io/node-sdk/classes/FingerprintServerApiClient.html#deleteVisitorData). ## API Reference -See the full [API reference](https://fingerprintjs.github.io/fingerprintjs-pro-server-api-node-sdk/). +See the full [API reference](https://fingerprintjs.github.io/node-sdk/). ## Semantic versioning @@ -201,8 +184,8 @@ See the full [API reference](https://fingerprintjs.github.io/fingerprintjs-pro-s ## Support and feedback -To report problems, ask questions, or provide feedback, please use [Issues](https://github.com/fingerprintjs/fingerprintjs-pro-server-api-node-sdk/issues). If you need private support, you can email us at [oss-support@fingerprint.com](mailto:oss-support@fingerprint.com). +To report problems, ask questions, or provide feedback, please use [Issues](https://github.com/fingerprintjs/node-sdk/issues). If you need private support, you can email us at [oss-support@fingerprint.com](mailto:oss-support@fingerprint.com). ## License -This project is licensed under the [MIT license](https://github.com/fingerprintjs/fingerprintjs-pro-server-api-node-sdk/tree/main/LICENSE). +This project is licensed under the [MIT license](https://github.com/fingerprintjs/node-sdk/tree/main/LICENSE). diff --git a/resources/fingerprint-server-api.yaml b/resources/fingerprint-server-api.yaml index 71a4fa75..db15fe10 100644 --- a/resources/fingerprint-server-api.yaml +++ b/resources/fingerprint-server-api.yaml @@ -1,14 +1,14 @@ -openapi: 3.0.3 +openapi: 3.1.1 info: - title: Fingerprint Server API + title: Server API description: > - Fingerprint Server API allows you to search, update, and delete - identification events in a server environment. It can be used for data - exports, decision-making, and data analysis scenarios. + Fingerprint Server API allows you to get, search, and update Events in a + server environment. It can be used for data exports, decision-making, and + data analysis scenarios. Server API is intended for server-side usage, it's not intended to be used from the client side, whether it's a browser or a mobile device. - version: '3' + version: '4' contact: name: Fingerprint Support email: support@fingerprint.com @@ -23,54 +23,65 @@ tags: analysis events or event history of individual visitors. externalDocs: description: API documentation - url: https://dev.fingerprint.com/reference/pro-server-api + url: https://docs.fingerprint.com/reference/server-api servers: - - url: https://api.fpjs.io + - url: https://api.fpjs.io/v4 description: Global - - url: https://eu.api.fpjs.io + - url: https://eu.api.fpjs.io/v4 description: EU - - url: https://ap.api.fpjs.io + - url: https://ap.api.fpjs.io/v4 description: Asia (Mumbai) security: - - ApiKeyHeader: [] - - ApiKeyQuery: [] + - bearerAuth: [] paths: - /events/{request_id}: + /events/{event_id}: get: tags: - Fingerprint operationId: getEvent - summary: Get event by request ID + summary: Get an event by event ID description: > Get a detailed analysis of an individual identification event, including - Smart Signals. + Smart Signals. - Please note that the response includes mobile signals (e.g. `rootApps`) - even if the request originated from a non-mobile platform. - It is highly recommended that you **ignore** the mobile signals for such - requests. - - - Use `requestId` as the URL path parameter. This API method is scoped to - a request, i.e. all returned information is by `requestId`. + Use `event_id` as the URL path parameter. This API method is scoped to a + request, i.e. all returned information is by `event_id`. parameters: - - name: request_id + - name: event_id in: path required: true schema: type: string description: >- The unique - [identifier](https://dev.fingerprint.com/reference/get-function#requestid) - of each identification request. + [identifier](https://docs.fingerprint.com/reference/js-agent-v4-get-function#event_id) + of each identification request (`requestId` can be used in its + place). + - name: ruleset_id + in: query + required: false + schema: + type: string + description: > + The ID of the ruleset to evaluate against the event, producing the + action to take for this event. + + The resulting action is returned in the `rule_action` attribute of + the response. responses: '200': description: OK. content: application/json: schema: - $ref: '#/components/schemas/EventsGetResponse' + $ref: '#/components/schemas/Event' + '400': + description: Bad request. The event Id provided is not valid. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' '403': description: Forbidden. Access to this API is denied. content: @@ -78,45 +89,64 @@ paths: schema: $ref: '#/components/schemas/ErrorResponse' '404': - description: >- - Not found. The request ID cannot be found in this application's - data. + description: Not found. The event Id cannot be found in this workspace's data. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '429': + description: Too Many Requests. The request is throttled. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: Workspace error. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' - put: + patch: tags: - Fingerprint operationId: updateEvent - summary: Update an event with a given request ID + summary: Update an event description: > - Change information in existing events specified by `requestId` or *flag + Change information in existing events specified by `event_id` or *flag suspicious events*. - When an event is created, it is assigned `linkedId` and `tag` submitted - through the JS agent parameters. This information might not be available - on the client so the Server API allows for updating the attributes after - the fact. + When an event is created, it can be assigned `linked_id` and `tags` + submitted through the JS agent parameters. + This information might not have been available on the client initially, + so the Server API permits updating these attributes after the fact. - **Warning** It's not possible to update events older than 10 days. + + **Warning** It's not possible to update events older than one month. + + + **Warning** Trying to update an event immediately after creation may + temporarily result in an + + error (HTTP 409 Conflict. The event is not mutable yet.) as the event is + fully propagated across our systems. In such a case, simply retry the + request. parameters: - - name: request_id + - name: event_id in: path required: true schema: type: string description: >- The unique event - [identifier](https://dev.fingerprint.com/reference/get-function#requestid). + [identifier](https://docs.fingerprint.com/reference/js-agent-v4-get-function#event_id). requestBody: required: true content: application/json: schema: - $ref: '#/components/schemas/EventsUpdateRequest' + $ref: '#/components/schemas/EventUpdate' responses: '200': description: OK. @@ -133,9 +163,7 @@ paths: schema: $ref: '#/components/schemas/ErrorResponse' '404': - description: >- - Not found. The request ID cannot be found in this application's - data. + description: Not found. The event Id cannot be found in this workspace's data. content: application/json: schema: @@ -146,29 +174,69 @@ paths: application/json: schema: $ref: '#/components/schemas/ErrorResponse' - /events/search: + /events: get: tags: - Fingerprint operationId: searchEvents - summary: Get events via search + x-dotnet-use-request-object: true + summary: Search events description: > - Search for identification events, including Smart Signals, using - multiple filtering criteria. If you don't provide `start` or `end` - parameters, the default search range is the last 7 days. + ## Search + + + The `/v4/events` endpoint provides a convenient way to search for past + events based on specific parameters. Typical use cases and queries + include: - Please note that events include mobile signals (e.g. `rootApps`) even if - the request originated from a non-mobile platform. We recommend you - **ignore** mobile signals for such requests. + - Searching for events associated with a single `visitor_id` within a + time range to get historical behavior of a visitor. + + - Searching for events associated with a single `linked_id` within a + time range to get all events associated with your internal account + identifier. + + - Excluding all bot traffic from the query (`good` and `bad` bots) + + + If you don't provide `start` or `end` parameters, the default search + range is the **last 7 days**. + + + ### Filtering events with the `suspect` flag + + + The `/v4/events` endpoint unlocks a powerful method for fraud protection + analytics. The `suspect` flag is exposed in all events where it was + previously set by the update API. + + + You can also apply the `suspect` query parameter as a filter to find all + potentially fraudulent activity that you previously marked as `suspect`. + This helps identify patterns of fraudulent behavior. + + + ### Environment scoping + + + If you use a secret key that is scoped to an environment, you will only + get events associated with the same environment. With a workspace-scoped + environment, you will get events from all environments. + + + Smart Signals not activated for your workspace or are not included in + the response. parameters: - name: limit in: query - required: true + required: false schema: type: integer format: int32 minimum: 1 + maximum: 100 + default: 10 example: 10 description: | Limit the number of events returned. @@ -177,77 +245,105 @@ paths: schema: type: string description: > - Use `pagination_key` to get the next page of results. + Use `pagination_key` to get the next page of results. - When more results are available (e.g., you requested up to 200 - results for your search using `limit`, but there are more than 200 - events total matching your request), the `paginationKey` top-level - attribute is added to the response. The key corresponds to the - `timestamp` of the last returned event. In the following request, - use that value in the `pagination_key` parameter to get the next - page of results: + When more results are available (e.g., you requested up to 100 + results for your query using `limit`, but there are more than 100 + events total matching your request), the `pagination_key` field is + added to the response. The pagination key is an arbitrary string + that should not be interpreted in any way and should be passed + as-is. In the following request, use that value in the + `pagination_key` parameter to get the next page of results: 1. First request, returning most recent 200 events: `GET - api-base-url/events/search?limit=200` + api-base-url/events?limit=100` - 2. Use `response.paginationKey` to get the next page of results: - `GET - api-base-url/events/search?limit=200&pagination_key=1740815825085` + 2. Use `response.pagination_key` to get the next page of results: + `GET api-base-url/events?limit=100&pagination_key=1740815825085` - name: visitor_id in: query schema: type: string description: > Unique [visitor - identifier](https://dev.fingerprint.com/reference/get-function#visitorid) - issued by Fingerprint Pro. + identifier](https://docs.fingerprint.com/reference/js-agent-v4-get-function#visitor_id) + issued by Fingerprint Identification and all active Smart Signals. Filter for events matching this `visitor_id`. - name: bot in: query schema: - type: string - enum: - - all - - good - - bad - - none + $ref: '#/components/schemas/SearchEventsBot' description: > - Filter events by the Bot Detection result, specifically: + Filter events by the Bot Detection result, specifically: `all` - events where any kind of bot was detected. `good` - events where a good bot was detected. `bad` - events where a bad bot was detected. `none` - events where no bot was detected. - > Note: When using this parameter, only events with the - `products.botd.data.bot.result` property set to a valid value are - returned. Events without a `products.botd` Smart Signal result are - left out of the response. + > Note: When using this parameter, only events with the `bot` + property set to a valid value are returned. Events without a `bot` + Smart Signal result are left out of the response. - name: ip_address in: query schema: type: string description: > - Filter events by IP address range. The range can be as specific as a - single IP (/32 for IPv4 or /128 for IPv6) + Filter events by IP address or IP range (if CIDR notation is used). + If CIDR notation is not used, a /32 for IPv4 or /128 for IPv6 is + assumed. + + Examples of range based queries: 10.0.0.0/24, 192.168.0.1/32 + - name: asn + in: query + schema: + type: string + description: > + Filter events by the ASN associated with the event's IP address. - All ip_address filters must use CIDR notation, for example, - 10.0.0.0/24, 192.168.0.1/32 + This corresponds to the `ip_info.(v4|v6).asn` property in the + response. - name: linked_id in: query schema: type: string description: > - Filter events by your custom identifier. + Filter events by your custom identifier. You can use [linked - IDs](https://dev.fingerprint.com/reference/get-function#linkedid) to - associate identification requests with your own identifier, for - example, session ID, purchase ID, or transaction ID. You can then + Ids](https://docs.fingerprint.com/reference/js-agent-v4-get-function#linkedid) + to associate identification requests with your own identifier, for + example, session Id, purchase Id, or transaction Id. You can then use this `linked_id` parameter to retrieve all events associated with your custom identifier. + - name: url + in: query + schema: + type: string + description: | + Filter events by the URL (`url` property) associated with the event. + - name: bundle_id + in: query + schema: + type: string + description: | + Filter events by the Bundle ID (iOS) associated with the event. + - name: package_name + in: query + schema: + type: string + description: > + Filter events by the Package Name (Android) associated with the + event. + - name: origin + in: query + schema: + type: string + description: > + Filter events by the origin field of the event. This is applicable + to web events only (e.g., https://example.com) - name: start in: query schema: @@ -276,7 +372,7 @@ paths: type: boolean description: > Filter events previously tagged as suspicious via the [Update - API](https://dev.fingerprint.com/reference/updateevent). + API](https://docs.fingerprint.com/reference/server-api-v4-update-event). > Note: When using this parameter, only events with the `suspect` property explicitly set to `true` or `false` are returned. Events @@ -286,143 +382,131 @@ paths: schema: type: boolean description: > - Filter events by VPN Detection result. + Filter events by VPN Detection result. - > Note: When using this parameter, only events with the - `products.vpn.data.result` property set to `true` or `false` are - returned. Events without a `products.vpn` Smart Signal result are - left out of the response. + > Note: When using this parameter, only events with the `vpn` + property set to `true` or `false` are returned. Events without a + `vpn` Smart Signal result are left out of the response. - name: virtual_machine in: query schema: type: boolean description: > - Filter events by Virtual Machine Detection result. + Filter events by Virtual Machine Detection result. > Note: When using this parameter, only events with the - `products.virtualMachine.data.result` property set to `true` or - `false` are returned. Events without a `products.virtualMachine` - Smart Signal result are left out of the response. + `virtual_machine` property set to `true` or `false` are returned. + Events without a `virtual_machine` Smart Signal result are left out + of the response. - name: tampering in: query schema: type: boolean description: > - Filter events by Tampering Detection result. + Filter events by Browser Tampering Detection result. > Note: When using this parameter, only events with the - `products.tampering.data.result` property set to `true` or `false` - are returned. Events without a `products.tampering` Smart Signal - result are left out of the response. + `tampering.result` property set to `true` or `false` are returned. + Events without a `tampering` Smart Signal result are left out of the + response. - name: anti_detect_browser in: query schema: type: boolean description: > - Filter events by Anti-detect Browser Detection result. + Filter events by Anti-detect Browser Detection result. > Note: When using this parameter, only events with the - `products.tampering.data.antiDetectBrowser` property set to `true` - or `false` are returned. Events without a `products.tampering` Smart - Signal result are left out of the response. + `tampering.anti_detect_browser` property set to `true` or `false` + are returned. Events without a `tampering` Smart Signal result are + left out of the response. - name: incognito in: query schema: type: boolean description: > - Filter events by Browser Incognito Detection result. + Filter events by Browser Incognito Detection result. - > Note: When using this parameter, only events with the - `products.incognito.data.result` property set to `true` or `false` - are returned. Events without a `products.incognito` Smart Signal - result are left out of the response. + > Note: When using this parameter, only events with the `incognito` + property set to `true` or `false` are returned. Events without an + `incognito` Smart Signal result are left out of the response. - name: privacy_settings in: query schema: type: boolean description: > - Filter events by Privacy Settings Detection result. + Filter events by Privacy Settings Detection result. > Note: When using this parameter, only events with the - `products.privacySettings.data.result` property set to `true` or - `false` are returned. Events without a `products.privacySettings` - Smart Signal result are left out of the response. + `privacy_settings` property set to `true` or `false` are returned. + Events without a `privacy_settings` Smart Signal result are left out + of the response. - name: jailbroken in: query schema: type: boolean description: > - Filter events by Jailbroken Device Detection result. + Filter events by Jailbroken Device Detection result. - > Note: When using this parameter, only events with the - `products.jailbroken.data.result` property set to `true` or `false` - are returned. Events without a `products.jailbroken` Smart Signal - result are left out of the response. + > Note: When using this parameter, only events with the `jailbroken` + property set to `true` or `false` are returned. Events without a + `jailbroken` Smart Signal result are left out of the response. - name: frida in: query schema: type: boolean description: > - Filter events by Frida Detection result. + Filter events by Frida Detection result. - > Note: When using this parameter, only events with the - `products.frida.data.result` property set to `true` or `false` are - returned. Events without a `products.frida` Smart Signal result are - left out of the response. + > Note: When using this parameter, only events with the `frida` + property set to `true` or `false` are returned. Events without a + `frida` Smart Signal result are left out of the response. - name: factory_reset in: query schema: type: boolean description: > - Filter events by Factory Reset Detection result. + Filter events by Factory Reset Detection result. - > Note: When using this parameter, only events with the - `products.factoryReset.data.result` property set to `true` or - `false` are returned. Events without a `products.factoryReset` Smart - Signal result are left out of the response. + > Note: When using this parameter, only events with a + `factory_reset` time. Events without a `factory_reset` Smart Signal + result are left out of the response. - name: cloned_app in: query schema: type: boolean description: > - Filter events by Cloned App Detection result. + Filter events by Cloned App Detection result. - > Note: When using this parameter, only events with the - `products.clonedApp.data.result` property set to `true` or `false` - are returned. Events without a `products.clonedApp` Smart Signal - result are left out of the response. + > Note: When using this parameter, only events with the `cloned_app` + property set to `true` or `false` are returned. Events without a + `cloned_app` Smart Signal result are left out of the response. - name: emulator in: query schema: type: boolean description: > - Filter events by Android Emulator Detection result. + Filter events by Android Emulator Detection result. - > Note: When using this parameter, only events with the - `products.emulator.data.result` property set to `true` or `false` - are returned. Events without a `products.emulator` Smart Signal - result are left out of the response. + > Note: When using this parameter, only events with the `emulator` + property set to `true` or `false` are returned. Events without an + `emulator` Smart Signal result are left out of the response. - name: root_apps in: query schema: type: boolean description: > - Filter events by Rooted Device Detection result. + Filter events by Rooted Device Detection result. - > Note: When using this parameter, only events with the - `products.rootApps.data.result` property set to `true` or `false` - are returned. Events without a `products.rootApps` Smart Signal - result are left out of the response. + > Note: When using this parameter, only events with the `root_apps` + property set to `true` or `false` are returned. Events without a + `root_apps` Smart Signal result are left out of the response. - name: vpn_confidence in: query schema: - type: string - enum: - - high - - medium - - low + $ref: '#/components/schemas/SearchEventsVpnConfidence' description: > - Filter events by VPN Detection result confidence level. + Filter events by VPN Detection result confidence level. `high` - events with high VPN Detection confidence. @@ -431,9 +515,8 @@ paths: `low` - events with low VPN Detection confidence. > Note: When using this parameter, only events with the - `products.vpn.data.confidence` property set to a valid value are - returned. Events without a `products.vpn` Smart Signal result are - left out of the response. + `vpn.confidence` property set to a valid value are returned. Events + without a `vpn` Smart Signal result are left out of the response. - name: min_suspect_score in: query schema: @@ -444,33 +527,9 @@ paths: threshold. > Note: When using this parameter, only events where the - `products.suspectScore.data.result` property set to a value - exceeding your threshold are returned. Events without a - `products.suspectScore` Smart Signal result are left out of the - response. - - name: ip_blocklist - in: query - schema: - type: boolean - description: > - Filter events by IP Blocklist Detection result. - - > Note: When using this parameter, only events with the - `products.ipBlocklist.data.result` property set to `true` or `false` - are returned. Events without a `products.ipBlocklist` Smart Signal - result are left out of the response. - - name: datacenter - in: query - schema: - type: boolean - description: > - Filter events by Datacenter Detection result. - - > Note: When using this parameter, only events with the - `products.ipInfo.data.v4.datacenter.result` or - `products.ipInfo.data.v6.datacenter.result` property set to `true` - or `false` are returned. Events without a `products.ipInfo` Smart - Signal result are left out of the response. + `suspect_score` property set to a value exceeding your threshold are + returned. Events without a `suspect_score` Smart Signal result are + left out of the response. - name: developer_tools in: query schema: @@ -479,9 +538,9 @@ paths: Filter events by Developer Tools detection result. > Note: When using this parameter, only events with the - `products.developerTools.data.result` property set to `true` or - `false` are returned. Events without a `products.developerTools` - Smart Signal result are left out of the response. + `developer_tools` property set to `true` or `false` are returned. + Events without a `developer_tools` Smart Signal result are left out + of the response. - name: location_spoofing in: query schema: @@ -490,9 +549,9 @@ paths: Filter events by Location Spoofing detection result. > Note: When using this parameter, only events with the - `products.locationSpoofing.data.result` property set to `true` or - `false` are returned. Events without a `products.locationSpoofing` - Smart Signal result are left out of the response. + `location_spoofing` property set to `true` or `false` are returned. + Events without a `location_spoofing` Smart Signal result are left + out of the response. - name: mitm_attack in: query schema: @@ -501,9 +560,9 @@ paths: Filter events by MITM (Man-in-the-Middle) Attack detection result. > Note: When using this parameter, only events with the - `products.mitmAttack.data.result` property set to `true` or `false` - are returned. Events without a `products.mitmAttack` Smart Signal - result are left out of the response. + `mitm_attack` property set to `true` or `false` are returned. Events + without a `mitm_attack` Smart Signal result are left out of the + response. - name: proxy in: query schema: @@ -511,45 +570,40 @@ paths: description: > Filter events by Proxy detection result. - > Note: When using this parameter, only events with the - `products.proxy.data.result` property set to `true` or `false` are - returned. Events without a `products.proxy` Smart Signal result are - left out of the response. + > Note: When using this parameter, only events with the `proxy` + property set to `true` or `false` are returned. Events without a + `proxy` Smart Signal result are left out of the response. - name: sdk_version in: query schema: type: string description: > Filter events by a specific SDK version associated with the - identification event. Example: `3.11.14` + identification event (`sdk.version` property). Example: `3.11.14` - name: sdk_platform in: query schema: - type: string - enum: - - js - - android - - ios + $ref: '#/components/schemas/SearchEventsSdkPlatform' description: > Filter events by the SDK Platform associated with the identification - event. + event (`sdk.platform` property) . - `js` - JavaScript agent (Web). + `js` - Javascript agent (Web). `ios` - Apple iOS based devices. `android` - Android based devices. - name: environment in: query - description: | - Filter for events by providing one or more environment IDs. + description: > + Filter for events by providing one or more environment IDs + (`environment_id` property). required: false schema: type: array items: type: string style: form - explode: true - name: proximity_id in: query schema: @@ -558,38 +612,36 @@ paths: Filter events by the most precise Proximity ID provided by default. > Note: When using this parameter, only events with the - `products.proximity.id` property matching the provided ID are - returned. Events without a `products.proximity` result are left out - of the response. - - name: proximity_precision_radius + `proximity.id` property matching the provided ID are returned. + Events without a `proximity` result are left out of the response. + - name: total_hits in: query schema: type: integer - format: int32 - enum: - - 10 - - 25 - - 65 - - 175 - - 450 - - 1200 - - 3300 - - 8500 - - 22500 + format: int64 + minimum: 1 + maximum: 1000 + description: > + When set, the response will include a `total_hits` property with a + count of total query matches across all pages, up to the specified + limit. + - name: tor_node + in: query + schema: + type: boolean description: > - Filter events by Proximity Radius. + Filter events by Tor Node detection result. - > Note: When using this parameter, only events with the - `products.proximity.precisionRadius` property set to a valid value - are returned. Events without a `products.proximity` result are left - out of the response. + > Note: When using this parameter, only events with the `tor_node` + property set to `true` or `false` are returned. Events without a + `tor_node` detection result are left out of the response. responses: '200': description: Events matching the filter(s). content: application/json: schema: - $ref: '#/components/schemas/SearchEventsResponse' + $ref: '#/components/schemas/EventSearch' '400': description: >- Bad request. One or more supplied search parameters are invalid, or @@ -604,162 +656,13 @@ paths: application/json: schema: $ref: '#/components/schemas/ErrorResponse' - /visitors/{visitor_id}: - get: - tags: - - Fingerprint - operationId: getVisits - summary: Get visits by visitor ID - description: > - Get a history of visits (identification events) for a specific - `visitorId`. Use the `visitorId` as a URL path parameter. - - Only information from the _Identification_ product is returned. - - - #### Headers - - - * `Retry-After` — Present in case of `429 Too many requests`. Indicates - how long you should wait before making a follow-up request. The value is - non-negative decimal integer indicating the seconds to delay after the - response is received. - x-flatten-optional-params: true - parameters: - - name: visitor_id - in: path - required: true - schema: - type: string - description: >- - Unique [visitor - identifier](https://dev.fingerprint.com/reference/get-function#visitorid) - issued by Fingerprint Pro. - - name: request_id - in: query - schema: - type: string - description: > - Filter visits by `requestId`. - - - Every identification request has a unique identifier associated with - it called `requestId`. This identifier is returned to the client in - the identification - [result](https://dev.fingerprint.com/reference/get-function#requestid). - When you filter visits by `requestId`, only one visit will be - returned. - x-go-skip-pointer: true - - name: linked_id - in: query - schema: - type: string - description: > - Filter visits by your custom identifier. - - - You can use - [`linkedId`](https://dev.fingerprint.com/reference/get-function#linkedid) - to associate identification requests with your own identifier, for - example: session ID, purchase ID, or transaction ID. You can then - use this `linked_id` parameter to retrieve all events associated - with your custom identifier. - x-go-skip-pointer: true - - name: limit - in: query - schema: - type: integer - format: int32 - minimum: 0 - description: > - Limit scanned results. - - - For performance reasons, the API first scans some number of events - before filtering them. Use `limit` to specify how many events are - scanned before they are filtered by `requestId` or `linkedId`. - Results are always returned sorted by the timestamp (most recent - first). - - By default, the most recent 100 visits are scanned, the maximum is - 500. - x-go-skip-pointer: true - - name: paginationKey - in: query - schema: - type: string - description: > - Use `paginationKey` to get the next page of results. - - - When more results are available (e.g., you requested 200 results - using `limit` parameter, but a total of 600 results are available), - the `paginationKey` top-level attribute is added to the response. - The key corresponds to the `requestId` of the last returned event. - In the following request, use that value in the `paginationKey` - parameter to get the next page of results: - - - 1. First request, returning most recent 200 events: `GET - api-base-url/visitors/:visitorId?limit=200` - - 2. Use `response.paginationKey` to get the next page of results: - `GET - api-base-url/visitors/:visitorId?limit=200&paginationKey=1683900801733.Ogvu1j` - - - Pagination happens during scanning and before filtering, so you can - get less visits than the `limit` you specified with more available - on the next page. When there are no more results available for - scanning, the `paginationKey` attribute is not returned. - x-go-skip-pointer: true - - name: before - in: query - deprecated: true - schema: - type: integer - format: int64 - minimum: 0 - description: > - ⚠️ Deprecated pagination method, please use `paginationKey` instead. - Timestamp (in milliseconds since epoch) used to paginate results. - x-go-skip-pointer: true - responses: - '200': - description: OK. - content: - application/json: - schema: - $ref: '#/components/schemas/VisitorsGetResponse' - '400': - description: >- - Bad request. The visitor ID or query parameters are missing or in - the wrong format. + '500': + description: Workspace error. content: application/json: schema: - $ref: '#/components/schemas/ErrorPlainResponse' - '403': - description: Forbidden. Access to this API is denied. - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorPlainResponse' - '429': - description: Too Many Requests. The request is throttled. - headers: - Retry-After: - description: >- - Indicates how many seconds you should wait before attempting the - next request. - schema: - type: integer - format: int32 - minimum: 0 - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorPlainResponse' + $ref: '#/components/schemas/ErrorResponse' + /visitors/{visitor_id}: delete: tags: - Fingerprint @@ -769,6 +672,7 @@ paths: Request deleting all data associated with the specified visitor ID. This API is useful for compliance with privacy regulations. + ### Which data is deleted? - Browser (or device) properties @@ -784,7 +688,7 @@ paths: - Upon request to delete, this data is deleted asynchronously (typically within a few minutes) and it will no longer be used to identify this browser (or device) for your [Fingerprint - Workspace](https://dev.fingerprint.com/docs/glossary#fingerprint-workspace). + Workspace](https://docs.fingerprint.com/docs/glossary#fingerprint-workspace). #### Identification requests made from this browser (or device) @@ -792,7 +696,7 @@ paths: - Fingerprint stores the identification requests made from a browser (or device) for up to 30 (or 90) days depending on your plan. To learn more, see [Data - Retention](https://dev.fingerprint.com/docs/regions#data-retention). + Retention](https://docs.fingerprint.com/docs/regions#data-retention). - Upon request to delete, the identification requests that were made by this browser @@ -806,15 +710,10 @@ paths: - If the same browser (or device) requests to identify, it will receive a different visitor ID. - - If you request [`/events` - API](https://dev.fingerprint.com/reference/getevent) with a `request_id` - that was made outside of the 10 days, you will still receive a valid - response. - - - If you request [`/visitors` - API](https://dev.fingerprint.com/reference/getvisits) for the deleted - visitor ID, the response will include identification requests that were - made outside of those 10 days. + - If you request [`/v4/events` + API](https://docs.fingerprint.com/reference/server-api-v4-get-event) + with an `event_id` that was made outside of the 10 days, you will still + receive a valid response. ### Interested? @@ -829,7 +728,7 @@ paths: type: string description: >- The [visitor - ID](https://dev.fingerprint.com/reference/get-function#visitorid) + ID](https://docs.fingerprint.com/reference/js-agent-v4-get-function#visitor_id) you want to delete. responses: '200': @@ -849,9 +748,7 @@ paths: schema: $ref: '#/components/schemas/ErrorResponse' '404': - description: >- - Not found. The visitor ID cannot be found in this application's - data. + description: Not found. The visitor ID cannot be found in this workspace's data. content: application/json: schema: @@ -862,246 +759,88 @@ paths: application/json: schema: $ref: '#/components/schemas/ErrorResponse' - /related-visitors: - get: - tags: - - Fingerprint - operationId: getRelatedVisitors - summary: Get Related Visitors +components: + securitySchemes: + bearerAuth: + type: http + scheme: bearer + bearerFormat: string + description: >- + Add your Secret API Key to the Authorization header using the standard + Bearer format: `Authorization: Bearer ` + schemas: + EventId: + type: string description: > - Related visitors API lets you link web visits and in-app browser visits - that originated from the same mobile device. - - It searches the past 6 months of identification events to find the - visitor IDs that belong to the same mobile device as the given visitor - ID. - - - ⚠️ Please note that this API is not enabled by default and is billable - separately. ⚠️ - - - If you would like to use Related visitors API, please contact our - [support team](https://fingerprint.com/support). - - To learn more, see [Related visitors API - reference](https://dev.fingerprint.com/reference/related-visitors-api). - parameters: - - name: visitor_id - in: query - required: true - schema: - type: string - description: >- - The [visitor - ID](https://dev.fingerprint.com/reference/get-function#visitorid) - for which you want to find the other visitor IDs that originated - from the same mobile device. - responses: - '200': - description: OK. - content: - application/json: - schema: - $ref: '#/components/schemas/RelatedVisitorsResponse' - '400': - description: >- - Bad request. The visitor ID parameter is missing or in the wrong - format. - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - '403': - description: Forbidden. Access to this API is denied. - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - '404': - description: >- - Not found. The visitor ID cannot be found in this application's - data. - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - '429': - description: Too Many Requests. The request is throttled. - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorResponse' - /webhook: - trace: - summary: Dummy path to describe webhook format. - tags: - - Fingerprint + Unique identifier of the user's request. The first portion of the + event_id is a unix epoch milliseconds timestamp For example: + `1758130560902.8tRtrH` + Timestamp: + description: Timestamp of the event with millisecond precision in Unix time. + type: integer + format: int64 + LinkedId: + type: string + description: A customer-provided id that was sent with the request. + EnvironmentId: + type: string + description: | + Environment Id of the event. For example: `ae_47abaca3db2c7c43` + Suspect: + type: boolean description: >- - Fake path to describe webhook format. More information about webhooks - can be found in the - [documentation](https://dev.fingerprint.com/docs/webhooks) - x-flatten-optional-params: true - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/Webhook' - responses: - default: - description: Dummy for the schema - callbacks: - webhook: - webhook: - post: - summary: Webhook example - description: >- - You can use HTTP basic authentication and set up credentials in - your [Fingerprint - account](https://dashboard.fingerprint.com/login) - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/Webhook' - responses: - default: - description: The server doesn't validate the answer. -components: - securitySchemes: - ApiKeyHeader: - type: apiKey - in: header - name: Auth-API-Key - ApiKeyQuery: - type: apiKey - in: query - name: api_key - schemas: - BrowserDetails: - type: object - additionalProperties: false - required: - - browserName - - browserFullVersion - - browserMajorVersion - - os - - osVersion - - device - - userAgent - properties: - browserName: - type: string - browserMajorVersion: - type: string - browserFullVersion: - type: string - os: - type: string - osVersion: - type: string - device: - type: string - userAgent: - type: string - GeolocationCity: - type: object - additionalProperties: false - required: - - name - properties: - name: - type: string - GeolocationCountry: + Field is `true` if you have previously set the `suspect` flag for this + event using the [Server API Update event + endpoint](https://docs.fingerprint.com/reference/server-api-v4-update-event). + Integration: type: object - additionalProperties: false - required: - - code - - name properties: - code: - type: string - minLength: 2 - maxLength: 2 name: type: string - GeolocationContinent: - type: object - additionalProperties: false - required: - - code - - name - properties: - code: - type: string - minLength: 2 - maxLength: 2 - name: + description: The name of the specific integration, e.g. "fingerprint-pro-react". + version: type: string - GeolocationSubdivision: + description: The version of the specific integration, e.g. "3.11.10". + subintegration: + type: object + properties: + name: + type: string + description: The name of the specific subintegration, e.g. "preact". + version: + type: string + description: The version of the specific subintegration, e.g. "10.21.0". + SDK: type: object - additionalProperties: false + description: Contains information about the SDK used to perform the request. required: - - isoCode - - name - properties: - isoCode: - type: string - name: - type: string - GeolocationSubdivisions: - type: array - items: - $ref: '#/components/schemas/GeolocationSubdivision' - DeprecatedGeolocation: - deprecated: true - type: object - description: >- - This field is **deprecated** and will not return a result for - **applications created after January 23rd, 2024**. Please use the [IP - Geolocation Smart - signal](https://dev.fingerprint.com/docs/smart-signals-overview#ip-geolocation) - for geolocation information. - additionalProperties: false + - platform + - version properties: - accuracyRadius: - type: integer - minimum: 0 - description: >- - The IP address is likely to be within this radius (in km) of the - specified location. - latitude: - type: number - format: double - minimum: -90 - maximum: 90 - longitude: - type: number - format: double - minimum: -180 - maximum: 180 - postalCode: + platform: type: string - timezone: + enum: + - js + - android + - ios + - unknown + description: Platform of the SDK used for the identification request. + version: type: string - format: timezone - city: - $ref: '#/components/schemas/GeolocationCity' - country: - $ref: '#/components/schemas/GeolocationCountry' - continent: - $ref: '#/components/schemas/GeolocationContinent' - subdivisions: - $ref: '#/components/schemas/GeolocationSubdivisions' - Tag: - type: object - description: >- - A customer-provided value or an object that was sent with identification - request. - additionalProperties: true + description: > + Version string of the SDK used for the identification request. For + example: `"3.12.1"` + integrations: + type: array + items: + $ref: '#/components/schemas/Integration' + Replayed: + type: boolean + description: > + `true` if we determined that this payload was replayed, `false` + otherwise. IdentificationConfidence: type: object - additionalProperties: false required: - score properties: @@ -1113,328 +852,294 @@ components: description: >- The confidence score is a floating-point number between 0 and 1 that represents the probability of accurate identification. - revision: + version: type: string description: >- - The revision name of the method used to calculate the Confidence + The version name of the method used to calculate the Confidence score. This field is only present for customers who opted in to an alternative calculation method. comment: type: string - IdentificationSeenAt: - type: object - additionalProperties: false - required: - - global - - subscription - properties: - global: - type: string - nullable: true - format: date-time - x-ogen-time-format: 2006-01-02T15:04:05.999Z07:00 - subscription: - type: string - nullable: true - format: date-time - x-ogen-time-format: 2006-01-02T15:04:05.999Z07:00 - RawDeviceAttributeError: - type: object - additionalProperties: false - properties: - name: - type: string - message: - type: string - RawDeviceAttribute: - type: object - additionalProperties: false - properties: - value: - title: value - error: - $ref: '#/components/schemas/RawDeviceAttributeError' - RawDeviceAttributes: - type: object - description: > - It includes 35+ raw browser identification attributes to provide - Fingerprint users with even more information than our standard visitor - ID provides. This enables Fingerprint users to not have to run our - open-source product in conjunction with Fingerprint Pro Plus and - Enterprise to get those additional attributes. - - Warning: The raw signals data can change at any moment as we improve the - product. We cannot guarantee the internal shape of raw device attributes - to be stable, so typical semantic versioning rules do not apply here. - Use this data with caution without assuming a specific structure beyond - the generic type provided here. - additionalProperties: - $ref: '#/components/schemas/RawDeviceAttribute' - SDK: - type: object - description: Contains information about the SDK used to perform the request. - additionalProperties: false - required: - - platform - - version - properties: - platform: - type: string - description: Platform of the SDK. - version: - type: string - description: SDK version string. Identification: type: object - additionalProperties: false required: - - visitorId - - requestId - - browserDetails - - incognito - - ip - - timestamp - - time - - url - - tag - - visitorFound - - firstSeenAt - - lastSeenAt - - replayed + - visitor_id + - visitor_found properties: - visitorId: + visitor_id: type: string description: >- String of 20 characters that uniquely identifies the visitor's browser or mobile device. - requestId: - type: string - description: Unique identifier of the user's request. - browserDetails: - $ref: '#/components/schemas/BrowserDetails' - incognito: - description: Flag if user used incognito session. - type: boolean - ip: - type: string - description: IP address of the requesting browser or bot. - ipLocation: - $ref: '#/components/schemas/DeprecatedGeolocation' - linkedId: - type: string - description: A customer-provided id that was sent with the request. - suspect: - description: >- - Field is `true` if you have previously set the `suspect` flag for - this event using the [Server API Update event - endpoint](https://dev.fingerprint.com/reference/updateevent). - type: boolean - timestamp: - description: Timestamp of the event with millisecond precision in Unix time. - type: integer - format: int64 - time: - type: string - format: date-time - x-ogen-time-format: 2006-01-02T15:04:05Z07:00 - description: >- - Time expressed according to ISO 8601 in UTC format, when the request - from the JS agent was made. We recommend to treat requests that are - older than 2 minutes as malicious. Otherwise, request replay attacks - are possible. - url: - type: string - description: Page URL from which the request was sent. - tag: - $ref: '#/components/schemas/Tag' confidence: $ref: '#/components/schemas/IdentificationConfidence' - visitorFound: + visitor_found: type: boolean description: Attribute represents if a visitor had been identified before. - firstSeenAt: - $ref: '#/components/schemas/IdentificationSeenAt' - lastSeenAt: - $ref: '#/components/schemas/IdentificationSeenAt' - components: - $ref: '#/components/schemas/RawDeviceAttributes' - replayed: - type: boolean + first_seen_at: + type: integer + format: int64 description: > - `true` if we determined that this payload was replayed, `false` - otherwise. - sdk: - $ref: '#/components/schemas/SDK' - environmentId: - type: string - description: Environment ID associated with the event - ErrorCode: - type: string - enum: - - RequestCannotBeParsed - - TokenRequired - - TokenNotFound - - SubscriptionNotActive - - WrongRegion - - FeatureNotEnabled - - RequestNotFound - - VisitorNotFound - - TooManyRequests - - 429 Too Many Requests - - StateNotReady - - Failed - description: | - Error code: - * `RequestCannotBeParsed` - the query parameters or JSON payload contains some errors - that prevented us from parsing it (wrong type/surpassed limits). - * `TokenRequired` - `Auth-API-Key` header is missing or empty. - * `TokenNotFound` - no Fingerprint application found for specified secret key. - * `SubscriptionNotActive` - Fingerprint application is not active. - * `WrongRegion` - server and application region differ. - * `FeatureNotEnabled` - this feature (for example, Delete API) is not enabled for your application. - * `RequestNotFound` - the specified request ID was not found. It never existed, expired, or it has been deleted. - * `VisitorNotFound` - The specified visitor ID was not found. It never existed or it may have already been deleted. - * `TooManyRequests` - the limit on secret API key requests per second has been exceeded. - * `429 Too Many Requests` - the limit on secret API key requests per second has been exceeded. - * `StateNotReady` - The event specified with request id is - not ready for updates yet. Try again. - This error happens in rare cases when update API is called immediately - after receiving the request id on the client. In case you need to send - information right away, we recommend using the JS agent API instead. - * `Failed` - internal server error. - Error: + Unix epoch time milliseconds timestamp indicating the time at which + this visitor ID was first seen. example: `1758069706642` - + Corresponding to Wed Sep 17 2025 00:41:46 GMT+0000 + last_seen_at: + type: integer + format: int64 + description: > + Unix epoch time milliseconds timestamp indicating the time at which + this visitor ID was last seen. example: `1758069706642` - + Corresponding to Wed Sep 17 2025 00:41:46 GMT+0000 + SupplementaryIDHighRecall: type: object - additionalProperties: false + description: >- + A supplementary browser identifier that prioritizes coverage over + precision. The High Recall ID algorithm matches more generously, i.e., + this identifier will remain the same even when there are subtle + differences between two requests. This algorithm does not create as many + new visitor IDs as the standard algorithms do, but there could be an + increase in false-positive identification. required: - - code - - message + - visitor_id + - visitor_found properties: - code: - $ref: '#/components/schemas/ErrorCode' - message: + visitor_id: type: string - ProductIdentification: + description: >- + String of 20 characters that uniquely identifies the visitor's + browser or mobile device. + visitor_found: + type: boolean + description: Attribute represents if a visitor had been identified before. + confidence: + $ref: '#/components/schemas/IdentificationConfidence' + first_seen_at: + type: integer + format: int64 + description: > + Unix epoch time milliseconds timestamp indicating the time at which + this ID was first seen. example: `1758069706642` - Corresponding to + Wed Sep 17 2025 00:41:46 GMT+0000 + last_seen_at: + type: integer + format: int64 + description: > + Unix epoch time milliseconds timestamp indicating the time at which + this ID was last seen. example: `1758069706642` - Corresponding to + Wed Sep 17 2025 00:41:46 GMT+0000 + Tags: type: object - additionalProperties: false - properties: - data: - $ref: '#/components/schemas/Identification' - error: - $ref: '#/components/schemas/Error' - BotdBotResult: + description: >- + A customer-provided value or an object that was sent with the + identification request or updated later. + additionalProperties: true + Url: type: string - enum: - - notDetected - - good - - bad - description: | - Bot detection result: - * `notDetected` - the visitor is not a bot - * `good` - good bot detected, such as Google bot, Baidu Spider, AlexaBot and so on - * `bad` - bad bot detected, such as Selenium, Puppeteer, Playwright, headless browsers, and so on - BotdBot: - type: object - description: Stores bot detection result - additionalProperties: false - required: - - result - properties: - result: - $ref: '#/components/schemas/BotdBotResult' - type: - type: string - Botd: + description: > + Page URL from which the request was sent. For example + `https://example.com/` + BundleId: + type: string + description: > + Bundle Id of the iOS application integrated with the Fingerprint SDK for + the event. For example: `com.foo.app` + PackageName: + type: string + description: > + Package name of the Android application integrated with the Fingerprint + SDK for the event. For example: `com.foo.app` + IpAddress: + type: string + description: IP address of the requesting browser or bot. + UserAgent: + type: string + description: > + User Agent of the client, for example: `Mozilla/5.0 (Windows NT 6.1; + Win64; x64) ....` + ClientReferrer: + type: string + description: > + Client Referrer field corresponds to the `document.referrer` field + gathered during an identification request. The value is an empty string + if the user navigated to the page directly (not through a link, but, for + example, by using a bookmark) For example: + `https://example.com/blog/my-article` + BrowserDetails: type: object - description: Contains all the information from Bot Detection product - additionalProperties: false required: - - bot - - url - - ip - - time - - userAgent - - requestId + - browser_name + - browser_full_version + - browser_major_version + - os + - os_version + - device properties: - bot: - $ref: '#/components/schemas/BotdBot' - meta: - $ref: '#/components/schemas/Tag' - linkedId: + browser_name: type: string - description: A customer-provided id that was sent with the request. - url: + browser_major_version: type: string - description: Page URL from which the request was sent. - ip: + browser_full_version: type: string - description: IP address of the requesting browser or bot. - time: + os: type: string - format: date-time - x-ogen-time-format: 2006-01-02T15:04:05.999Z07:00 - description: >- - Time in UTC when the request from the JS agent was made. We - recommend to treat requests that are older than 2 minutes as - malicious. Otherwise, request replay attacks are possible. - userAgent: + os_version: type: string - requestId: + device: type: string - description: Unique identifier of the user's request. - ProductBotd: - type: object - additionalProperties: false - properties: - data: - $ref: '#/components/schemas/Botd' - error: - $ref: '#/components/schemas/Error' - RootApps: + Proximity: type: object - additionalProperties: false + description: > + Proximity ID represents a fixed geographical zone in a discrete global + grid within which the device is observed. required: - - result - properties: - result: - type: boolean - description: > - Android specific root management apps detection. There are 2 - values: - * `true` - Root Management Apps detected (e.g. Magisk). - * `false` - No Root Management Apps detected or the client isn't Android. - ProductRootApps: - type: object - additionalProperties: false + - id + - precision_radius + - confidence properties: - data: - $ref: '#/components/schemas/RootApps' - error: - $ref: '#/components/schemas/Error' - Emulator: + id: + type: string + description: | + A stable privacy-preserving identifier for a given proximity zone. + precision_radius: + type: integer + format: int32 + enum: + - 10 + - 25 + - 65 + - 175 + - 450 + - 1200 + - 3300 + - 8500 + - 22500 + description: | + The radius of the proximity zone’s precision level, in meters. + confidence: + type: number + format: float + minimum: 0 + maximum: 1 + description: > + A value between `0` and `1` representing the likelihood that the + true device location lies within the mapped proximity zone. + * Scores closer to `1` indicate high confidence that the location is inside the mapped proximity zone. + * Scores closer to `0` indicate lower confidence, suggesting the true location may fall in an adjacent zone. + BotResult: + type: string + enum: + - bad + - good + - not_detected + description: | + Bot detection result: + * `bad` - bad bot detected, such as Selenium, Puppeteer, Playwright, headless browsers, and so on + * `good` - good bot detected, such as Google bot, Baidu Spider, AlexaBot and so on + * `not_detected` - the visitor is not a bot + BotType: + type: string + description: | + Additional classification of the bot type if detected. + BotInfo: type: object - additionalProperties: false + description: Extended bot information. required: - - result + - category + - provider + - name + - identity + - confidence properties: - result: - type: boolean + category: + type: string + description: The type and purpose of the bot. + provider: + type: string + description: The organization or company operating the bot. + provider_url: + type: string + description: The URL of the bot provider's website. + name: + type: string + description: The specific name or identifier of the bot. + identity: + type: string + enum: + - verified + - signed + - spoofed + - unknown description: | - Android specific emulator detection. There are 2 values: - * `true` - Emulated environment detected (e.g. launch inside of AVD). - * `false` - No signs of emulated environment detected or the client is not Android. - ProductEmulator: + The verification status of the bot's identity: + * `verified` - well-known bot with publicly verifiable identity, directed by the bot provider. + * `signed` - bot that signs its platform via Web Bot Auth, directed by the bot provider’s customers. + * `spoofed` - bot that claims a public identity but fails verification. + * `unknown` - bot that does not publish a verifiable identity. + confidence: + type: string + enum: + - low + - medium + - high + description: Confidence level of the bot identification. + ClonedApp: + type: boolean + description: > + Android specific cloned application detection. There are 2 values: * + `true` - Presence of app cloners work detected (e.g. fully cloned + application found or launch of it inside of a not main working profile + detected). * `false` - No signs of cloned application detected or the + client is not Android. + DeveloperTools: + type: boolean + description: > + `true` if the browser is Chrome with DevTools open or Firefox with + Developer Tools open, `false` otherwise. + Emulator: + type: boolean + description: > + Android specific emulator detection. There are 2 values: + + * `true` - Emulated environment detected (e.g. launch inside of AVD). + + * `false` - No signs of emulated environment detected or the client is + not Android. + FactoryReset: + type: integer + format: int64 + description: > + The time of the most recent factory reset that happened on the **mobile + device** is expressed as Unix epoch time. When a factory reset cannot be + detected on the mobile device or when the request is initiated from a + browser, this field will correspond to the *epoch* time (i.e 1 Jan 1970 + UTC) as a value of 0. See [Factory Reset + Detection](https://docs.fingerprint.com/docs/smart-signals-reference#factory-reset-detection) + to learn more about this Smart Signal. + Frida: + type: boolean + description: > + [Frida](https://frida.re/docs/) detection for Android and iOS devices. + There are 2 values: + + * `true` - Frida detected + + * `false` - No signs of Frida or the client is not a mobile device. + IPBlockList: type: object - additionalProperties: false properties: - data: - $ref: '#/components/schemas/Emulator' - error: - $ref: '#/components/schemas/Error' + email_spam: + type: boolean + description: IP address was part of a known email spam attack (SMTP). + attack_source: + type: boolean + description: IP address was part of a known network attack (SSH/HTTPS). + tor_node: + type: boolean + description: IP address was part of known TOR network activity. Geolocation: type: object - additionalProperties: false properties: - accuracyRadius: + accuracy_radius: type: integer minimum: 0 description: >- @@ -1450,50 +1155,41 @@ components: format: double minimum: -180 maximum: 180 - postalCode: + postal_code: type: string timezone: type: string format: timezone - city: - $ref: '#/components/schemas/GeolocationCity' - country: - $ref: '#/components/schemas/GeolocationCountry' - continent: - $ref: '#/components/schemas/GeolocationContinent' - subdivisions: - $ref: '#/components/schemas/GeolocationSubdivisions' - IPInfoASN: - type: object - additionalProperties: false - required: - - asn - - name - - network - properties: - asn: + city_name: type: string - name: + country_code: type: string - network: + minLength: 2 + maxLength: 2 + country_name: type: string - IPInfoDataCenter: - type: object - additionalProperties: false - required: - - result - - name - properties: - result: - type: boolean - name: + continent_code: + type: string + minLength: 2 + maxLength: 2 + continent_name: type: string + subdivisions: + type: array + items: + type: object + required: + - iso_code + - name + properties: + iso_code: + type: string + name: + type: string IPInfoV4: type: object - additionalProperties: false required: - address - - geolocation properties: address: type: string @@ -1501,15 +1197,21 @@ components: geolocation: $ref: '#/components/schemas/Geolocation' asn: - $ref: '#/components/schemas/IPInfoASN' - datacenter: - $ref: '#/components/schemas/IPInfoDataCenter' + type: string + asn_name: + type: string + asn_network: + type: string + asn_type: + type: string + datacenter_result: + type: boolean + datacenter_name: + type: string IPInfoV6: type: object - additionalProperties: false required: - address - - geolocation properties: address: type: string @@ -1517,193 +1219,49 @@ components: geolocation: $ref: '#/components/schemas/Geolocation' asn: - $ref: '#/components/schemas/IPInfoASN' - datacenter: - $ref: '#/components/schemas/IPInfoDataCenter' + type: string + asn_name: + type: string + asn_network: + type: string + asn_type: + type: string + datacenter_result: + type: boolean + datacenter_name: + type: string IPInfo: type: object description: >- Details about the request IP address. Has separate fields for v4 and v6 IP address versions. - additionalProperties: false properties: v4: $ref: '#/components/schemas/IPInfoV4' v6: $ref: '#/components/schemas/IPInfoV6' - ProductIPInfo: - type: object - additionalProperties: false - properties: - data: - $ref: '#/components/schemas/IPInfo' - error: - $ref: '#/components/schemas/Error' - IPBlocklistDetails: - type: object - additionalProperties: false - required: - - emailSpam - - attackSource - properties: - emailSpam: - type: boolean - description: IP address was part of a known email spam attack (SMTP). - attackSource: - type: boolean - description: IP address was part of a known network attack (SSH/HTTPS). - IPBlocklist: - type: object - additionalProperties: false - required: - - result - - details - properties: - result: - type: boolean - description: > - `true` if request IP address is part of any database that we use to - search for known malicious actors, `false` otherwise. - details: - $ref: '#/components/schemas/IPBlocklistDetails' - ProductIPBlocklist: - type: object - additionalProperties: false - properties: - data: - $ref: '#/components/schemas/IPBlocklist' - error: - $ref: '#/components/schemas/Error' - Tor: - type: object - additionalProperties: false - required: - - result - properties: - result: - type: boolean - description: > - `true` if the request IP address is a known tor exit node, `false` - otherwise. - ProductTor: - type: object - additionalProperties: false - properties: - data: - $ref: '#/components/schemas/Tor' - error: - $ref: '#/components/schemas/Error' - VPNConfidence: - type: string - enum: - - low - - medium - - high - description: >- - A confidence rating for the VPN detection result — "low", "medium", or - "high". Depends on the combination of results returned from all VPN - detection methods. - VPNMethods: - type: object - additionalProperties: false - required: - - timezoneMismatch - - publicVPN - - auxiliaryMobile - - osMismatch - - relay - properties: - timezoneMismatch: - type: boolean - description: >- - The browser timezone doesn't match the timezone inferred from the - request IP address. - publicVPN: - type: boolean - description: >- - Request IP address is owned and used by a public VPN service - provider. - auxiliaryMobile: - type: boolean - description: >- - This method applies to mobile devices only. Indicates the result of - additional methods used to detect a VPN in mobile devices. - osMismatch: - type: boolean - description: >- - The browser runs on a different operating system than the operating - system inferred from the request network signature. - relay: - type: boolean - description: > - Request IP address belongs to a relay service provider, indicating - the use of relay services like [Apple Private - relay](https://support.apple.com/en-us/102602) or [Cloudflare - Warp](https://developers.cloudflare.com/warp-client/). - - - * Like VPNs, relay services anonymize the visitor's true IP address. - - * Unlike traditional VPNs, relay services don't let visitors spoof - their location by choosing an exit node in a different country. - - - This field allows you to differentiate VPN users and relay service - users in your fraud prevention logic. - VPN: - type: object - additionalProperties: false - required: - - result - - confidence - - originTimezone - - originCountry - - methods - properties: - result: - type: boolean - description: >- - VPN or other anonymizing service has been used when sending the - request. - confidence: - $ref: '#/components/schemas/VPNConfidence' - originTimezone: - type: string - description: Local timezone which is used in timezoneMismatch method. - originCountry: - type: string - description: >- - Country of the request (only for Android SDK version >= 2.4.0, ISO - 3166 format or unknown). - methods: - $ref: '#/components/schemas/VPNMethods' - ProductVPN: - type: object - additionalProperties: false - properties: - data: - $ref: '#/components/schemas/VPN' - error: - $ref: '#/components/schemas/Error' + Proxy: + type: boolean + description: > + IP address was used by a public proxy provider or belonged to a known + recent residential proxy ProxyConfidence: type: string enum: - low - medium - high - description: | - Confidence level of the proxy detection. - If a proxy is not detected, confidence is "high". - If it's detected, can be "low", "medium", or "high". + description: > + Confidence level of the proxy detection. If a proxy is not detected, + confidence is "high". If it's detected, can be "low", "medium", or + "high". ProxyDetails: type: object - nullable: true - additionalProperties: false - description: Proxy detection details (present if proxy is detected) + description: Proxy detection details (present if `proxy` is `true`) required: - - proxyType + - proxy_type properties: - proxyType: + proxy_type: type: string enum: - residential @@ -1713,1282 +1271,1122 @@ components: legitimate traffic, while data center proxies are public proxies hosted in data centers - lastSeenAt: + last_seen_at: + type: integer + format: int64 + description: > + Unix millisecond timestamp with hourly resolution of when this IP + was last seen as a proxy + provider: type: string - format: date-time - x-ogen-time-format: 2006-01-02T15:00:00.000Z - description: | - ISO 8601 formatted timestamp in UTC with hourly resolution - of when this IP was last seen as a proxy when available. - Proxy: - type: object - additionalProperties: false - required: - - result - - confidence - properties: - result: - type: boolean description: > - IP address was used by a public proxy provider or belonged to a - known recent residential proxy - confidence: - $ref: '#/components/schemas/ProxyConfidence' - details: - $ref: '#/components/schemas/ProxyDetails' - ProductProxy: - type: object - additionalProperties: false - properties: - data: - $ref: '#/components/schemas/Proxy' - error: - $ref: '#/components/schemas/Error' + String representing the last proxy service provider detected when + this + + IP was synced. An IP can be shared by multiple service providers. Incognito: + type: boolean + description: > + `true` if we detected incognito mode used in the browser, `false` + otherwise. + Jailbroken: + type: boolean + description: | + iOS specific jailbreak detection. There are 2 values: + * `true` - Jailbreak detected. + * `false` - No signs of jailbreak or the client is not iOS. + LocationSpoofing: + type: boolean + description: >- + Flag indicating whether the request came from a mobile device with + location spoofing enabled. + MitMAttack: + type: boolean + description: > + * `true` - When requests made from your users' mobile devices to + Fingerprint servers have been intercepted and potentially modified. + + * `false` - Otherwise or when the request originated from a browser. + + See [MitM Attack + Detection](https://docs.fingerprint.com/docs/smart-signals-reference#mitm-attack-detection) + to learn more about this Smart Signal. + PrivacySettings: + type: boolean + description: > + `true` if the request is from a privacy aware browser (e.g. Tor) or from + a browser in which fingerprinting is blocked. Otherwise `false`. + RootApps: + type: boolean + description: > + Android specific root management apps detection. There are 2 values: + + * `true` - Root Management Apps detected (e.g. Magisk). + + * `false` - No Root Management Apps detected or the client isn't + Android. + RulesetId: + type: string + description: The ID of the evaluated ruleset. + RuleId: + type: string + description: The ID of the rule that matched the identification event. + RuleExpression: + type: string + description: The expression of the rule that matched the identification event. + RuleActionType: + type: string + description: Describes the action to take with the request. + enum: + - allow + - block + RuleActionHeaderField: type: object - additionalProperties: false required: - - result - properties: - result: - type: boolean - description: > - `true` if we detected incognito mode used in the browser, `false` - otherwise. - ProductIncognito: - type: object - additionalProperties: false + - name + - value properties: - data: - $ref: '#/components/schemas/Incognito' - error: - $ref: '#/components/schemas/Error' - Tampering: + name: + type: string + description: The header field name. + value: + type: string + description: The value of the header field. + RequestHeaderModifications: type: object - additionalProperties: false - required: - - result - - anomalyScore - - antiDetectBrowser + description: >- + The set of header modifications to apply, in the following order: + remove, set, append. properties: - result: - type: boolean - description: > - Indicates if an identification request from a browser or an Android - SDK has been tampered with. Not supported in the iOS SDK, is always - `false` for iOS requests. - * `true` - If the request meets either of the following conditions: - * Contains anomalous browser or device attributes that could not have been legitimately produced by the JavaScript agent or the Android SDK (see `anomalyScore`). - * Originated from an anti-detect browser like Incognition (see `antiDetectBrowser`). - * `false` - If the request is considered genuine or was generated by the iOS SDK. - anomalyScore: - type: number - format: double - minimum: 0 - maximum: 1 - description: > - A score that indicates the extent of anomalous data in the request. - This field applies to requests originating from **both** browsers - and Android SDKs. - * Values above `0.5` indicate that the request has been tampered with. - * Values below `0.5` indicate that the request is genuine. - antiDetectBrowser: - type: boolean - description: > - Anti-detect browsers try to evade identification by masking or - manipulating their fingerprint to imitate legitimate browser - configurations. This field does not apply to requests originating - from mobile SDKs. - * `true` - The browser resembles a known anti-detect browser, for example, Incognition. - * `false` - The browser does not resemble an anti-detect browser or the request originates from a mobile SDK. - ProductTampering: - type: object - additionalProperties: false - properties: - data: - $ref: '#/components/schemas/Tampering' - error: - $ref: '#/components/schemas/Error' - ClonedApp: - type: object - additionalProperties: false - required: - - result - properties: - result: - type: boolean - description: | - Android specific cloned application detection. There are 2 values: - * `true` - Presence of app cloners work detected (e.g. fully cloned application found or launch of it inside of a not main working profile detected). - * `false` - No signs of cloned application detected or the client is not Android. - ProductClonedApp: - type: object - additionalProperties: false - properties: - data: - $ref: '#/components/schemas/ClonedApp' - error: - $ref: '#/components/schemas/Error' - FactoryReset: - type: object - additionalProperties: false - required: - - time - - timestamp - properties: - time: - type: string - format: date-time - description: > - Indicates the time (in UTC) of the most recent factory reset that - happened on the **mobile device**. - - When a factory reset cannot be detected on the mobile device or when - the request is initiated from a browser, this field will correspond - to the *epoch* time (i.e 1 Jan 1970 UTC). - - See [Factory Reset - Detection](https://dev.fingerprint.com/docs/smart-signals-overview#factory-reset-detection) - to learn more about this Smart Signal. - timestamp: - type: integer - format: int64 - description: > - This field is just another representation of the value in the `time` - field. - - The time of the most recent factory reset that happened on the - **mobile device** is expressed as Unix epoch time. - ProductFactoryReset: - type: object - additionalProperties: false - properties: - data: - $ref: '#/components/schemas/FactoryReset' - error: - $ref: '#/components/schemas/Error' - Jailbroken: - type: object - additionalProperties: false - required: - - result - properties: - result: - type: boolean - description: | - iOS specific jailbreak detection. There are 2 values: - * `true` - Jailbreak detected. - * `false` - No signs of jailbreak or the client is not iOS. - ProductJailbroken: - type: object - additionalProperties: false - properties: - data: - $ref: '#/components/schemas/Jailbroken' - error: - $ref: '#/components/schemas/Error' - Frida: - type: object - additionalProperties: false - required: - - result - properties: - result: - type: boolean - description: > - [Frida](https://frida.re/docs/) detection for Android and iOS - devices. There are 2 values: - * `true` - Frida detected - * `false` - No signs of Frida or the client is not a mobile device. - ProductFrida: - type: object - additionalProperties: false - properties: - data: - $ref: '#/components/schemas/Frida' - error: - $ref: '#/components/schemas/Error' - PrivacySettings: - type: object - additionalProperties: false - required: - - result - properties: - result: - type: boolean - description: > - `true` if the request is from a privacy aware browser (e.g. Tor) or - from a browser in which fingerprinting is blocked. Otherwise - `false`. - ProductPrivacySettings: - type: object - additionalProperties: false - properties: - data: - $ref: '#/components/schemas/PrivacySettings' - error: - $ref: '#/components/schemas/Error' - VirtualMachine: - type: object - additionalProperties: false - required: - - result - properties: - result: - type: boolean - description: > - `true` if the request came from a browser running inside a virtual - machine (e.g. VMWare), `false` otherwise. - ProductVirtualMachine: - type: object - additionalProperties: false - properties: - data: - $ref: '#/components/schemas/VirtualMachine' - error: - $ref: '#/components/schemas/Error' - ProductRawDeviceAttributes: + remove: + type: array + description: The list of headers to remove. + items: + type: string + set: + type: array + description: >- + The list of headers to set, overwriting any existing headers with + the same name. + items: + $ref: '#/components/schemas/RuleActionHeaderField' + append: + type: array + description: The list of headers to append. + items: + $ref: '#/components/schemas/RuleActionHeaderField' + EventRuleActionAllow: + description: >- + Informs the client that the request should be forwarded to the origin + with optional request header modifications. type: object - additionalProperties: false properties: - data: - $ref: '#/components/schemas/RawDeviceAttributes' - error: - $ref: '#/components/schemas/Error' - HighActivity: - type: object - additionalProperties: false + ruleset_id: + $ref: '#/components/schemas/RulesetId' + rule_id: + $ref: '#/components/schemas/RuleId' + rule_expression: + $ref: '#/components/schemas/RuleExpression' + type: + allOf: + - $ref: '#/components/schemas/RuleActionType' + - const: allow + request_header_modifications: + $ref: '#/components/schemas/RequestHeaderModifications' required: - - result - properties: - result: - type: boolean - description: Flag indicating if the request came from a high-activity visitor. - dailyRequests: - type: integer - format: int64 - minimum: 1 - description: Number of requests from the same visitor in the previous day. - ProductHighActivity: + - ruleset_id + - type + StatusCode: + type: integer + description: A valid HTTP status code. + RuleActionBody: + type: string + description: The response body to send to the client. + EventRuleActionBlock: + description: >- + Informs the client the request should be blocked using the response + described by this rule action. type: object - additionalProperties: false properties: - data: - $ref: '#/components/schemas/HighActivity' - error: - $ref: '#/components/schemas/Error' - LocationSpoofing: - type: object - additionalProperties: false + ruleset_id: + $ref: '#/components/schemas/RulesetId' + rule_id: + $ref: '#/components/schemas/RuleId' + rule_expression: + $ref: '#/components/schemas/RuleExpression' + type: + allOf: + - $ref: '#/components/schemas/RuleActionType' + - const: block + status_code: + $ref: '#/components/schemas/StatusCode' + headers: + type: array + description: A list of headers to send. + items: + $ref: '#/components/schemas/RuleActionHeaderField' + body: + $ref: '#/components/schemas/RuleActionBody' required: - - result - properties: - result: - type: boolean - description: >- - Flag indicating whether the request came from a mobile device with - location spoofing enabled. - ProductLocationSpoofing: + - ruleset_id + - type + EventRuleAction: type: object - additionalProperties: false - properties: - data: - $ref: '#/components/schemas/LocationSpoofing' - error: - $ref: '#/components/schemas/Error' + description: >- + Describes the action the client should take, according to the rule in + the ruleset that matched the event. When getting an event by event ID, + the rule_action will only be included when the ruleset_id query + parameter is specified. + oneOf: + - $ref: '#/components/schemas/EventRuleActionAllow' + - $ref: '#/components/schemas/EventRuleActionBlock' + discriminator: + propertyName: type + mapping: + allow: '#/components/schemas/EventRuleActionAllow' + block: '#/components/schemas/EventRuleActionBlock' SuspectScore: + type: integer + description: > + Suspect Score is an easy way to integrate Smart Signals into your fraud + protection work flow. It is a weighted representation of all Smart + Signals present in the payload that helps identify suspicious activity. + The value range is [0; S] where S is sum of all Smart Signals weights. + See more details here: https://docs.fingerprint.com/docs/suspect-score + Tampering: + type: boolean + description: > + Flag indicating browser tampering was detected. This happens when + either: + * There are inconsistencies in the browser configuration that cross internal tampering thresholds (see `tampering_details.anomaly_score`). + * The browser signature resembles an "anti-detect" browser specifically designed to evade fingerprinting (see `tampering_details.anti_detect_browser`). + TamperingDetails: type: object - additionalProperties: false - required: - - result - properties: - result: - type: integer - description: > - Suspect Score is an easy way to integrate Smart Signals into your - fraud protection work flow. It is a weighted representation of all - Smart Signals present in the payload that helps identify suspicious - activity. The value range is [0; S] where S is sum of all Smart - Signals weights. See more details here: - https://dev.fingerprint.com/docs/suspect-score - ProductSuspectScore: - type: object - additionalProperties: false - properties: - data: - $ref: '#/components/schemas/SuspectScore' - error: - $ref: '#/components/schemas/Error' - RemoteControl: - type: object - deprecated: true - description: | - This signal is deprecated. - additionalProperties: false - required: - - result properties: - result: + anomaly_score: + type: number + format: double + minimum: 0 + maximum: 1 + x-platforms: + - android + - ios + - browser + description: | + Confidence score (`0.0 - 1.0`) for tampering detection: + * Values above `0.5` indicate tampering. + * Values below `0.5` indicate genuine browsers. + anti_detect_browser: type: boolean + x-platforms: + - browser description: > - `true` if the request came from a machine being remotely controlled - (e.g. TeamViewer), `false` otherwise. - ProductRemoteControl: - type: object - deprecated: true - description: | - This product is deprecated. - additionalProperties: false - properties: - data: - $ref: '#/components/schemas/RemoteControl' - error: - $ref: '#/components/schemas/Error' - VelocityIntervals: + True if the identified browser resembles an "anti-detect" browser, + such as Incognition, which attempts to evade identification by + manipulating its fingerprint. + VelocityData: type: object description: > Is absent if the velocity data could not be generated for the visitor - ID. - additionalProperties: false + Id. required: - - 5m - - 1h + - 5_minutes + - 1_hour properties: - 5m: + 5_minutes: type: integer - 1h: + description: > + Count for the last 5 minutes of velocity data, from the time of the + event. + 1_hour: type: integer - 24h: + description: > + Count for the last 1 hour of velocity data, from the time of the + event. + 24_hours: type: integer description: > - The `24h` interval of `distinctIp`, `distinctLinkedId`, - `distinctCountry`, `distinctIpByLinkedId` and - `distinctVisitorIdByLinkedId` will be omitted if the number of - `events`` for the visitor ID in the last 24 hours - (`events.intervals.['24h']`) is higher than 20.000. - VelocityData: - type: object - additionalProperties: false - properties: - intervals: - $ref: '#/components/schemas/VelocityIntervals' + The `24_hours` interval of `distinct_ip`, `distinct_linked_id`, + `distinct_country`, `distinct_ip_by_linked_id` and + `distinct_visitor_id_by_linked_id` will be omitted if the number of + `events` for the visitor Id in the last 24 hours + (`events.['24_hours']`) is higher than 20.000. Velocity: type: object description: > - Sums key data points for a specific `visitorId`, `ipAddress` and - `linkedId` at three distinct time + Sums key data points for a specific `visitor_id`, `ip_address` and + `linked_id` at three distinct time intervals: 5 minutes, 1 hour, and 24 hours as follows: - - Number of distinct IP addresses associated to the visitor ID. + - Number of distinct IP addresses associated to the visitor Id. - - Number of distinct linked IDs associated with the visitor ID. + - Number of distinct linked Ids associated with the visitor Id. - - Number of distinct countries associated with the visitor ID. + - Number of distinct countries associated with the visitor Id. - - Number of identification events associated with the visitor ID. + - Number of identification events associated with the visitor Id. - Number of identification events associated with the detected IP address. - Number of distinct IP addresses associated with the provided linked - ID. + Id. - - Number of distinct visitor IDs associated with the provided linked ID. + - Number of distinct visitor Ids associated with the provided linked Id. - The `24h` interval of `distinctIp`, `distinctLinkedId`, - `distinctCountry`, + The `24h` interval of `distinct_ip`, `distinct_linked_id`, + `distinct_country`, - `distinctIpByLinkedId` and `distinctVisitorIdByLinkedId` will be - omitted + `distinct_ip_by_linked_id` and `distinct_visitor_id_by_linked_id` will + be omitted - if the number of `events` for the visitor ID in the last 24 + if the number of `events` for the visitor Id in the last 24 - hours (`events.intervals.['24h']`) is higher than 20.000. - additionalProperties: false - required: - - distinctIp - - distinctLinkedId - - distinctCountry - - events - - ipEvents - - distinctIpByLinkedId - - distinctVisitorIdByLinkedId + hours (`events.['24h']`) is higher than 20.000. + + + All will not necessarily be returned in a response, some may be omitted + if the + + associated event does not have the required data, such as a linked_id. properties: - distinctIp: + distinct_ip: $ref: '#/components/schemas/VelocityData' - distinctLinkedId: + distinct_linked_id: $ref: '#/components/schemas/VelocityData' - distinctCountry: + distinct_country: $ref: '#/components/schemas/VelocityData' events: $ref: '#/components/schemas/VelocityData' - ipEvents: + ip_events: $ref: '#/components/schemas/VelocityData' - distinctIpByLinkedId: + distinct_ip_by_linked_id: $ref: '#/components/schemas/VelocityData' - distinctVisitorIdByLinkedId: + distinct_visitor_id_by_linked_id: $ref: '#/components/schemas/VelocityData' - ProductVelocity: - type: object - additionalProperties: false - properties: - data: - $ref: '#/components/schemas/Velocity' - error: - $ref: '#/components/schemas/Error' - DeveloperTools: + VirtualMachine: + type: boolean + description: > + `true` if the request came from a browser running inside a virtual + machine (e.g. VMWare), `false` otherwise. + Vpn: + type: boolean + description: | + VPN or other anonymizing service has been used when sending the request. + VpnConfidence: + type: string + enum: + - low + - medium + - high + description: >- + A confidence rating for the VPN detection result — "low", "medium", or + "high". Depends on the combination of results returned from all VPN + detection methods. + VpnOriginTimezone: + type: string + description: | + Local timezone which is used in timezone_mismatch method. + VpnOriginCountry: + type: string + description: > + Country of the request (only for Android SDK version >= 2.4.0, ISO 3166 + format or unknown). + VpnMethods: type: object - additionalProperties: false - required: - - result properties: - result: + timezone_mismatch: type: boolean - description: > - `true` if the browser is Chrome with DevTools open or Firefox with - Developer Tools open, `false` otherwise. - ProductDeveloperTools: - type: object - additionalProperties: false - properties: - data: - $ref: '#/components/schemas/DeveloperTools' - error: - $ref: '#/components/schemas/Error' - MitMAttack: - type: object - additionalProperties: false - required: - - result - properties: - result: + x-platforms: + - android + - ios + - browser + description: >- + The browser timezone doesn't match the timezone inferred from the + request IP address. + public_vpn: + type: boolean + x-platforms: + - android + - ios + - browser + description: >- + Request IP address is owned and used by a public VPN service + provider. + auxiliary_mobile: + type: boolean + x-platforms: + - android + - ios + - browser + description: >- + This method applies to mobile devices only. Indicates the result of + additional methods used to detect a VPN in mobile devices. + os_mismatch: + type: boolean + x-platforms: + - browser + description: >- + The browser runs on a different operating system than the operating + system inferred from the request network signature. + relay: type: boolean + x-platforms: + - android + - ios + - browser description: > - * `true` - When requests made from your users' mobile devices to - Fingerprint servers have been intercepted and potentially modified. + Request IP address belongs to a relay service provider, indicating + the use of relay services like [Apple Private + relay](https://support.apple.com/en-us/102602) or [Cloudflare + Warp](https://developers.cloudflare.com/warp-client/). + - * `false` - Otherwise or when the request originated from a browser. + * Like VPNs, relay services anonymize the visitor's true IP address. + + * Unlike traditional VPNs, relay services don't let visitors spoof + their location by choosing an exit node in a different country. - See [MitM Attack - Detection](https://dev.fingerprint.com/docs/smart-signals-reference#mitm-attack-detection) - to learn more about this Smart Signal. - ProductMitMAttack: + + This field allows you to differentiate VPN users and relay service + users in your fraud prevention logic. + HighActivity: + type: boolean + description: Flag indicating if the request came from a high-activity visitor. + FontPreferences: type: object - additionalProperties: false + description: > + Baseline measurement of canonical fonts rendered on the device. Numeric + width metrics, in CSS pixels, for the canonical fonts collected by the + agent. properties: - data: - $ref: '#/components/schemas/MitMAttack' - error: - $ref: '#/components/schemas/Error' - Proximity: + default: + type: number + format: double + serif: + type: number + format: double + sans: + type: number + format: double + mono: + type: number + format: double + apple: + type: number + format: double + min: + type: number + format: double + system: + type: number + format: double + Emoji: type: object - description: > - Proximity ID represents a fixed geographical zone in a discrete global - grid within which the device is observed. - additionalProperties: false - required: - - id - - precisionRadius - - confidence + description: Bounding box metrics describing how the emoji glyph renders. properties: - id: + font: type: string - description: | - A stable privacy-preserving identifier for a given proximity zone. - precisionRadius: - type: integer - format: int32 - enum: - - 10 - - 25 - - 65 - - 175 - - 450 - - 1200 - - 3300 - - 8500 - - 22500 - description: | - The radius of the proximity zone’s precision level, in meters. - confidence: + description: Font family reported by the browser when drawing the emoji. + width: type: number - format: float - minimum: 0 - maximum: 1 - description: > - A value between `0` and `1` representing the likelihood that the - true device location lies within the mapped proximity zone. - * Scores closer to `1` indicate high confidence that the location is inside the mapped proximity zone. - * Scores closer to `0` indicate lower confidence, suggesting the true location may fall in an adjacent zone. - ProductProximity: - type: object - additionalProperties: false - properties: - data: - $ref: '#/components/schemas/Proximity' - error: - $ref: '#/components/schemas/Error' - Products: - type: object - description: >- - Contains all information about the request identified by `requestId`, - depending on the pricing plan (Pro, Pro Plus, Enterprise) - additionalProperties: false - properties: - identification: - $ref: '#/components/schemas/ProductIdentification' - botd: - $ref: '#/components/schemas/ProductBotd' - rootApps: - $ref: '#/components/schemas/ProductRootApps' - emulator: - $ref: '#/components/schemas/ProductEmulator' - ipInfo: - $ref: '#/components/schemas/ProductIPInfo' - ipBlocklist: - $ref: '#/components/schemas/ProductIPBlocklist' - tor: - $ref: '#/components/schemas/ProductTor' - vpn: - $ref: '#/components/schemas/ProductVPN' - proxy: - $ref: '#/components/schemas/ProductProxy' - incognito: - $ref: '#/components/schemas/ProductIncognito' - tampering: - $ref: '#/components/schemas/ProductTampering' - clonedApp: - $ref: '#/components/schemas/ProductClonedApp' - factoryReset: - $ref: '#/components/schemas/ProductFactoryReset' - jailbroken: - $ref: '#/components/schemas/ProductJailbroken' - frida: - $ref: '#/components/schemas/ProductFrida' - privacySettings: - $ref: '#/components/schemas/ProductPrivacySettings' - virtualMachine: - $ref: '#/components/schemas/ProductVirtualMachine' - rawDeviceAttributes: - $ref: '#/components/schemas/ProductRawDeviceAttributes' - highActivity: - $ref: '#/components/schemas/ProductHighActivity' - locationSpoofing: - $ref: '#/components/schemas/ProductLocationSpoofing' - suspectScore: - $ref: '#/components/schemas/ProductSuspectScore' - remoteControl: - $ref: '#/components/schemas/ProductRemoteControl' - velocity: - $ref: '#/components/schemas/ProductVelocity' - developerTools: - $ref: '#/components/schemas/ProductDeveloperTools' - mitmAttack: - $ref: '#/components/schemas/ProductMitMAttack' - proximity: - $ref: '#/components/schemas/ProductProximity' - EventsGetResponse: - type: object - description: >- - Contains results from all activated products - Fingerprint Pro, Bot - Detection, and others. - additionalProperties: false - required: - - products - properties: - products: - $ref: '#/components/schemas/Products' - ErrorResponse: - type: object - additionalProperties: false - required: - - error - properties: - error: - $ref: '#/components/schemas/Error' - EventsUpdateRequest: + format: double + height: + type: number + format: double + top: + type: number + format: double + bottom: + type: number + format: double + left: + type: number + format: double + right: + type: number + format: double + x: + type: number + format: double + 'y': + type: number + format: double + Fonts: + type: array + description: List of fonts detected on the device. + items: + type: string + example: + - Arial Unicode MS + - Gill Sans + - Helvetica Neue + - Menlo + DeviceMemory: + type: integer + format: int32 + minimum: 0 + example: 8 + description: Rounded amount of RAM (in gigabytes) reported by the browser. + Timezone: + type: string + description: Timezone identifier detected on the client. + Canvas: type: object + description: Canvas fingerprint containing winding flag plus geometry/text hashes. properties: - linkedId: - type: string - description: LinkedID value to assign to the existing event - tag: - $ref: '#/components/schemas/Tag' - suspect: + winding: type: boolean - description: Suspect flag indicating observed suspicious or fraudulent event - x-go-force-pointer: true - SearchEventsResponse: - type: object - description: >- - Contains a list of all identification events matching the specified - search criteria. - additionalProperties: false - properties: - events: - type: array - items: - type: object - description: Device intelligence results for the identification event. - required: - - products - properties: - products: - $ref: '#/components/schemas/Products' - paginationKey: + geometry: type: string - description: >- - Use this value in the `pagination_key` parameter to request the next - page of search results. - Visit: + description: Hash of geometry rendering output or `unsupported` markers. + text: + type: string + description: Hash of text rendering output or `unsupported` markers. + Languages: + type: array + description: > + Navigator languages reported by the agent including fallbacks. Each + inner array represents ordered language preferences reported by + different APIs. + items: + type: array + items: + type: string + WebGlExtensions: type: object - additionalProperties: false - required: - - requestId - - browserDetails - - incognito - - ip - - timestamp - - time - - url - - tag - - visitorFound - - firstSeenAt - - lastSeenAt + description: Hashes of WebGL context attributes and extension support. properties: - requestId: + context_attributes: type: string - description: Unique identifier of the user's request. - browserDetails: - $ref: '#/components/schemas/BrowserDetails' - incognito: - type: boolean - description: Flag if user used incognito session. - ip: + parameters: type: string - description: IP address of the requesting browser or bot. - ipLocation: - $ref: '#/components/schemas/DeprecatedGeolocation' - linkedId: + shader_precisions: type: string - description: A customer-provided id that was sent with the request. - timestamp: - type: integer - format: int64 - description: Timestamp of the event with millisecond precision in Unix time. - time: + extensions: type: string - format: date-time - x-ogen-time-format: 2006-01-02T15:04:05Z07:00 - description: >- - Time expressed according to ISO 8601 in UTC format, when the request - from the client agent was made. We recommend to treat requests that - are older than 2 minutes as malicious. Otherwise, request replay - attacks are possible. - url: + extension_parameters: type: string - description: Page URL from which the request was sent. - tag: - $ref: '#/components/schemas/Tag' - confidence: - $ref: '#/components/schemas/IdentificationConfidence' - visitorFound: - type: boolean - description: Attribute represents if a visitor had been identified before. - firstSeenAt: - $ref: '#/components/schemas/IdentificationSeenAt' - lastSeenAt: - $ref: '#/components/schemas/IdentificationSeenAt' - components: - $ref: '#/components/schemas/RawDeviceAttributes' - VisitorsGetResponse: - type: object - description: >- - Pagination-related fields `lastTimestamp` and `paginationKey` are - included if you use a pagination parameter like `limit` or `before` and - there is more data available on the next page. - additionalProperties: false - required: - - visitorId - - visits - properties: - visitorId: - type: string - visits: + unsupported_extensions: type: array items: - $ref: '#/components/schemas/Visit' - lastTimestamp: - deprecated: true - type: integer - format: int64 - description: > - ⚠️ Deprecated paging attribute, please use `paginationKey` instead. - Timestamp of the last visit in the current page of results. - paginationKey: - type: string - description: >- - Request ID of the last visit in the current page of results. Use - this value in the following request as the `paginationKey` parameter - to get the next page of results. - ErrorPlainResponse: + type: string + WebGlBasics: type: object - additionalProperties: false - required: - - error + description: Render and vendor strings reported by the WebGL context. properties: - error: + version: type: string - RelatedVisitor: - type: object - additionalProperties: false - required: - - visitorId - properties: - visitorId: + vendor: type: string - description: >- - Visitor ID of a browser that originates from the same mobile device - as the input visitor ID. - RelatedVisitorsResponse: - type: object - additionalProperties: false - required: - - relatedVisitors - properties: - relatedVisitors: - type: array - items: - $ref: '#/components/schemas/RelatedVisitor' - WebhookRootApps: - type: object - additionalProperties: false - properties: - result: - type: boolean - description: > - Android specific root management apps detection. There are 2 - values: - * `true` - Root Management Apps detected (e.g. Magisk). - * `false` - No Root Management Apps detected or the client isn't Android. - WebhookEmulator: - type: object - additionalProperties: false - properties: - result: - type: boolean - description: | - Android specific emulator detection. There are 2 values: - * `true` - Emulated environment detected (e.g. launch inside of AVD). - * `false` - No signs of emulated environment detected or the client is not Android. - WebhookIPInfo: - type: object - description: >- - Details about the request IP address. Has separate fields for v4 and v6 - IP address versions. - additionalProperties: false - properties: - v4: - $ref: '#/components/schemas/IPInfoV4' - v6: - $ref: '#/components/schemas/IPInfoV6' - WebhookIPBlocklist: - type: object - additionalProperties: false - properties: - result: - type: boolean - description: > - `true` if request IP address is part of any database that we use to - search for known malicious actors, `false` otherwise. - details: - $ref: '#/components/schemas/IPBlocklistDetails' - WebhookTor: - type: object - additionalProperties: false - properties: - result: - type: boolean - description: > - `true` if the request IP address is a known tor exit node, `false` - otherwise. - WebhookVPN: - type: object - additionalProperties: false - properties: - result: - type: boolean - description: >- - VPN or other anonymizing service has been used when sending the - request. - confidence: - $ref: '#/components/schemas/VPNConfidence' - originTimezone: + vendor_unmasked: type: string - description: Local timezone which is used in timezoneMismatch method. - originCountry: + renderer: type: string - description: >- - Country of the request (only for Android SDK version >= 2.4.0, ISO - 3166 format or unknown). - methods: - $ref: '#/components/schemas/VPNMethods' - WebhookProxy: - type: object - additionalProperties: false - properties: - result: - type: boolean - description: > - IP address was used by a public proxy provider or belonged to a - known recent residential proxy - confidence: - $ref: '#/components/schemas/ProxyConfidence' - details: - $ref: '#/components/schemas/ProxyDetails' - WebhookTampering: + renderer_unmasked: + type: string + shading_language_version: + type: string + ScreenResolution: + type: array + description: Current screen resolution. + minItems: 2 + maxItems: 2 + items: + type: integer + format: int32 + TouchSupport: type: object - additionalProperties: false + description: Browser-reported touch capabilities. properties: - result: + touch_event: type: boolean - description: > - Indicates if an identification request from a browser or an Android - SDK has been tampered with. Not supported in the iOS SDK, is always - `false` for iOS requests. - * `true` - If the request meets either of the following conditions: - * Contains anomalous browser or device attributes that could not have been legitimately produced by the JavaScript agent or the Android SDK (see `anomalyScore`). - * Originated from an anti-detect browser like Incognition (see `antiDetectBrowser`). - * `false` - If the request is considered genuine or was generated by the iOS SDK. - anomalyScore: - type: number - format: double - minimum: 0 - maximum: 1 - description: > - A score that indicates the extent of anomalous data in the request. - This field applies to requests originating from **both** browsers - and Android SDKs. - * Values above `0.5` indicate that the request has been tampered with. - * Values below `0.5` indicate that the request is genuine. - antiDetectBrowser: + touch_start: type: boolean - description: > - Anti-detect browsers try to evade identification by masking or - manipulating their fingerprint to imitate legitimate browser - configurations. This field does not apply to requests originating - from mobile SDKs. - * `true` - The browser resembles a known anti-detect browser, for example, Incognition. - * `false` - The browser does not resemble an anti-detect browser or the request originates from a mobile SDK. - WebhookClonedApp: - type: object - additionalProperties: false - properties: - result: - type: boolean - description: | - Android specific cloned application detection. There are 2 values: - * `true` - Presence of app cloners work detected (e.g. fully cloned application found or launch of it inside of a not main working profile detected). - * `false` - No signs of cloned application detected or the client is not Android. - WebhookFactoryReset: - type: object - additionalProperties: false - properties: - time: - type: string - format: date-time - description: > - Indicates the time (in UTC) of the most recent factory reset that - happened on the **mobile device**. - - When a factory reset cannot be detected on the mobile device or when - the request is initiated from a browser, this field will correspond - to the *epoch* time (i.e 1 Jan 1970 UTC). - - See [Factory Reset - Detection](https://dev.fingerprint.com/docs/smart-signals-overview#factory-reset-detection) - to learn more about this Smart Signal. - timestamp: + max_touch_points: type: integer format: int64 - description: > - This field is just another representation of the value in the `time` - field. - - The time of the most recent factory reset that happened on the - **mobile device** is expressed as Unix epoch time. - WebhookJailbroken: - type: object - additionalProperties: false - properties: - result: - type: boolean - description: | - iOS specific jailbreak detection. There are 2 values: - * `true` - Jailbreak detected. - * `false` - No signs of jailbreak or the client is not iOS. - WebhookFrida: - type: object - additionalProperties: false - properties: - result: - type: boolean - description: > - [Frida](https://frida.re/docs/) detection for Android and iOS - devices. There are 2 values: - * `true` - Frida detected - * `false` - No signs of Frida or the client is not a mobile device. - WebhookPrivacySettings: - type: object - additionalProperties: false - properties: - result: - type: boolean - description: > - `true` if the request is from a privacy aware browser (e.g. Tor) or - from a browser in which fingerprinting is blocked. Otherwise - `false`. - WebhookVirtualMachine: - type: object - additionalProperties: false - properties: - result: - type: boolean - description: > - `true` if the request came from a browser running inside a virtual - machine (e.g. VMWare), `false` otherwise. - WebhookRawDeviceAttributes: + Oscpu: + type: string + description: Navigator `oscpu` string. + Architecture: + type: integer + format: int32 + description: Integer representing the CPU architecture exposed by the browser. + CookiesEnabled: + type: boolean + description: Whether the cookies are enabled in the browser. + HardwareConcurrency: + type: integer + format: int32 + minimum: 1 + description: Number of logical CPU cores reported by the browser. + DateTimeLocale: + type: string + description: > + Locale derived from the Intl.DateTimeFormat API. Negative values + indicate known error states. The negative statuses can be: - "-1": A + permanent status for browsers that don't support Intl API. - "-2": A + permanent status for browsers that don't supportDateTimeFormat + constructor. - "-3": A permanent status for browsers in which + DateTimeFormat locale is undefined or null. + Vendor: + type: string + description: Navigator vendor string. + ColorDepth: + type: integer + format: int32 + description: Screen color depth in bits. + Platform: + type: string + description: Navigator platform string. + SessionStorage: + type: boolean + description: Whether sessionStorage is available. + LocalStorage: + type: boolean + description: Whether localStorage is available. + Audio: + type: number + format: double + description: > + AudioContext fingerprint or negative status when unavailable. The + negative statuses can be: - -1: A permanent status for those browsers + which are known to always suspend audio context - -2: A permanent status + for browsers that don't support the signal - -3: A temporary status that + means that an unexpected timeout has happened + Plugins: + type: array + description: Browser plugins reported by `navigator.plugins`. + items: + type: object + properties: + name: + type: string + description: + type: string + mimeTypes: + type: array + items: + type: object + properties: + type: + type: string + suffixes: + type: string + description: + type: string + required: + - name + IndexedDb: + type: boolean + description: Whether IndexedDB is available. + Math: + type: string + description: Hash of Math APIs used for entropy collection. + RawDeviceAttributes: type: object description: > - It includes 35+ raw browser identification attributes to provide - Fingerprint users with even more information than our standard visitor - ID provides. This enables Fingerprint users to not have to run our - open-source product in conjunction with Fingerprint Pro Plus and - Enterprise to get those additional attributes. - - Warning: The raw signals data can change at any moment as we improve the - product. We cannot guarantee the internal shape of raw device attributes - to be stable, so typical semantic versioning rules do not apply here. - Use this data with caution without assuming a specific structure beyond - the generic type provided here. - additionalProperties: - $ref: '#/components/schemas/RawDeviceAttribute' - WebhookHighActivity: + A curated subset of raw browser/device attributes that the API surface + exposes. Each property contains a value or object with the data for the + collected signal. + properties: + font_preferences: + $ref: '#/components/schemas/FontPreferences' + emoji: + $ref: '#/components/schemas/Emoji' + fonts: + $ref: '#/components/schemas/Fonts' + device_memory: + $ref: '#/components/schemas/DeviceMemory' + timezone: + $ref: '#/components/schemas/Timezone' + canvas: + $ref: '#/components/schemas/Canvas' + languages: + $ref: '#/components/schemas/Languages' + webgl_extensions: + $ref: '#/components/schemas/WebGlExtensions' + webgl_basics: + $ref: '#/components/schemas/WebGlBasics' + screen_resolution: + $ref: '#/components/schemas/ScreenResolution' + touch_support: + $ref: '#/components/schemas/TouchSupport' + oscpu: + $ref: '#/components/schemas/Oscpu' + architecture: + $ref: '#/components/schemas/Architecture' + cookies_enabled: + $ref: '#/components/schemas/CookiesEnabled' + hardware_concurrency: + $ref: '#/components/schemas/HardwareConcurrency' + date_time_locale: + $ref: '#/components/schemas/DateTimeLocale' + vendor: + $ref: '#/components/schemas/Vendor' + color_depth: + $ref: '#/components/schemas/ColorDepth' + platform: + $ref: '#/components/schemas/Platform' + session_storage: + $ref: '#/components/schemas/SessionStorage' + local_storage: + $ref: '#/components/schemas/LocalStorage' + audio: + $ref: '#/components/schemas/Audio' + plugins: + $ref: '#/components/schemas/Plugins' + indexed_db: + $ref: '#/components/schemas/IndexedDb' + math: + $ref: '#/components/schemas/Math' + Event: type: object - additionalProperties: false + description: >- + Contains results from Fingerprint Identification and all active Smart + Signals. required: - - result - properties: - result: - type: boolean - description: Flag indicating if the request came from a high-activity visitor. - dailyRequests: - type: integer - format: int64 - minimum: 1 - description: Number of requests from the same visitor in the previous day. - WebhookLocationSpoofing: - type: object - additionalProperties: false - properties: - result: - type: boolean - description: >- - Flag indicating whether the request came from a mobile device with - location spoofing enabled. - WebhookSuspectScore: - type: object - additionalProperties: false - properties: - result: - type: integer - description: > - Suspect Score is an easy way to integrate Smart Signals into your - fraud protection work flow. It is a weighted representation of all - Smart Signals present in the payload that helps identify suspicious - activity. The value range is [0; S] where S is sum of all Smart - Signals weights. See more details here: - https://dev.fingerprint.com/docs/suspect-score - WebhookRemoteControl: - type: object - deprecated: true - description: | - This signal is deprecated. - additionalProperties: false + - event_id + - timestamp properties: - result: - type: boolean - description: > - `true` if the request came from a machine being remotely controlled - (e.g. TeamViewer), `false` otherwise. - WebhookVelocity: - type: object + event_id: + $ref: '#/components/schemas/EventId' + x-platforms: + - android + - ios + - browser + timestamp: + $ref: '#/components/schemas/Timestamp' + x-platforms: + - android + - ios + - browser + linked_id: + $ref: '#/components/schemas/LinkedId' + x-platforms: + - android + - ios + - browser + environment_id: + $ref: '#/components/schemas/EnvironmentId' + x-platforms: + - android + - ios + - browser + suspect: + $ref: '#/components/schemas/Suspect' + x-platforms: + - android + - ios + - browser + sdk: + $ref: '#/components/schemas/SDK' + x-platforms: + - android + - ios + - browser + replayed: + $ref: '#/components/schemas/Replayed' + x-platforms: + - android + - ios + - browser + identification: + $ref: '#/components/schemas/Identification' + x-platforms: + - android + - ios + - browser + supplementary_id_high_recall: + $ref: '#/components/schemas/SupplementaryIDHighRecall' + x-platforms: + - android + - ios + - browser + tags: + $ref: '#/components/schemas/Tags' + x-platforms: + - android + - ios + - browser + url: + $ref: '#/components/schemas/Url' + x-platforms: + - browser + bundle_id: + $ref: '#/components/schemas/BundleId' + x-platforms: + - ios + package_name: + $ref: '#/components/schemas/PackageName' + x-platforms: + - android + ip_address: + $ref: '#/components/schemas/IpAddress' + x-platforms: + - android + - ios + - browser + user_agent: + $ref: '#/components/schemas/UserAgent' + x-platforms: + - android + - ios + - browser + client_referrer: + $ref: '#/components/schemas/ClientReferrer' + x-platforms: + - browser + browser_details: + $ref: '#/components/schemas/BrowserDetails' + x-platforms: + - browser + proximity: + $ref: '#/components/schemas/Proximity' + x-platforms: + - android + - ios + bot: + $ref: '#/components/schemas/BotResult' + x-platforms: + - browser + bot_type: + $ref: '#/components/schemas/BotType' + x-platforms: + - browser + bot_info: + $ref: '#/components/schemas/BotInfo' + x-platforms: + - browser + cloned_app: + $ref: '#/components/schemas/ClonedApp' + x-platforms: + - android + developer_tools: + $ref: '#/components/schemas/DeveloperTools' + x-platforms: + - browser + emulator: + $ref: '#/components/schemas/Emulator' + x-platforms: + - android + factory_reset_timestamp: + $ref: '#/components/schemas/FactoryReset' + x-platforms: + - android + - ios + frida: + $ref: '#/components/schemas/Frida' + x-platforms: + - android + - ios + ip_blocklist: + $ref: '#/components/schemas/IPBlockList' + x-platforms: + - android + - ios + - browser + ip_info: + $ref: '#/components/schemas/IPInfo' + x-platforms: + - android + - ios + - browser + proxy: + $ref: '#/components/schemas/Proxy' + x-platforms: + - android + - ios + - browser + proxy_confidence: + $ref: '#/components/schemas/ProxyConfidence' + x-platforms: + - android + - ios + - browser + proxy_details: + $ref: '#/components/schemas/ProxyDetails' + x-platforms: + - android + - ios + - browser + incognito: + $ref: '#/components/schemas/Incognito' + x-platforms: + - browser + jailbroken: + $ref: '#/components/schemas/Jailbroken' + x-platforms: + - ios + location_spoofing: + $ref: '#/components/schemas/LocationSpoofing' + x-platforms: + - android + - ios + mitm_attack: + $ref: '#/components/schemas/MitMAttack' + x-platforms: + - android + - ios + privacy_settings: + $ref: '#/components/schemas/PrivacySettings' + x-platforms: + - browser + root_apps: + $ref: '#/components/schemas/RootApps' + x-platforms: + - android + rule_action: + $ref: '#/components/schemas/EventRuleAction' + suspect_score: + $ref: '#/components/schemas/SuspectScore' + x-platforms: + - android + - ios + - browser + tampering: + $ref: '#/components/schemas/Tampering' + x-platforms: + - android + - ios + - browser + tampering_details: + $ref: '#/components/schemas/TamperingDetails' + x-platforms: + - android + - ios + - browser + velocity: + $ref: '#/components/schemas/Velocity' + x-platforms: + - android + - ios + - browser + virtual_machine: + $ref: '#/components/schemas/VirtualMachine' + x-platforms: + - browser + vpn: + $ref: '#/components/schemas/Vpn' + x-platforms: + - android + - ios + - browser + vpn_confidence: + $ref: '#/components/schemas/VpnConfidence' + x-platforms: + - android + - ios + - browser + vpn_origin_timezone: + $ref: '#/components/schemas/VpnOriginTimezone' + x-platforms: + - android + - ios + - browser + vpn_origin_country: + $ref: '#/components/schemas/VpnOriginCountry' + x-platforms: + - android + - ios + vpn_methods: + $ref: '#/components/schemas/VpnMethods' + x-platforms: + - android + - ios + - browser + high_activity_device: + $ref: '#/components/schemas/HighActivity' + x-platforms: + - android + - ios + - browser + raw_device_attributes: + $ref: '#/components/schemas/RawDeviceAttributes' + x-platforms: + - browser + ErrorCode: + type: string + enum: + - request_cannot_be_parsed + - secret_api_key_required + - secret_api_key_not_found + - public_api_key_required + - public_api_key_not_found + - subscription_not_active + - wrong_region + - feature_not_enabled + - request_not_found + - visitor_not_found + - too_many_requests + - state_not_ready + - failed + - event_not_found + - missing_module + - payload_too_large + - service_unavailable + - ruleset_not_found description: > - Sums key data points for a specific `visitorId`, `ipAddress` and - `linkedId` at three distinct time + Error code: - intervals: 5 minutes, 1 hour, and 24 hours as follows: + * `request_cannot_be_parsed` - The query parameters or JSON payload + contains some errors + that prevented us from parsing it (wrong type/surpassed limits). + * `secret_api_key_required` - secret API key in header is missing or + empty. + * `secret_api_key_not_found` - No Fingerprint workspace found for + specified secret API key. - - Number of distinct IP addresses associated to the visitor ID. + * `public_api_key_required` - public API key in header is missing or + empty. - - Number of distinct linked IDs associated with the visitor ID. + * `public_api_key_not_found` - No Fingerprint workspace found for + specified public API key. - - Number of distinct countries associated with the visitor ID. + * `subscription_not_active` - Fingerprint workspace is not active. - - Number of identification events associated with the visitor ID. + * `wrong_region` - Server and workspace region differ. - - Number of identification events associated with the detected IP - address. + * `feature_not_enabled` - This feature (for example, Delete API) is not + enabled for your workspace. - - Number of distinct IP addresses associated with the provided linked - ID. + * `request_not_found` - The specified event ID was not found. It never + existed, expired, or it has been deleted. - - Number of distinct visitor IDs associated with the provided linked ID. + * `visitor_not_found` - The specified visitor ID was not found. It never + existed or it may have already been deleted. + * `too_many_requests` - The limit on secret API key requests per second + has been exceeded. - The `24h` interval of `distinctIp`, `distinctLinkedId`, - `distinctCountry`, + * `state_not_ready` - The event specified with event ID is + not ready for updates yet. Try again. + This error happens in rare cases when update API is called immediately + after receiving the event ID on the client. In case you need to send + information right away, we recommend using the JS agent API instead. + * `failed` - Internal server error. - `distinctIpByLinkedId` and `distinctVisitorIdByLinkedId` will be - omitted + * `event_not_found` - The specified event ID was not found. It never + existed, expired, or it has been deleted. - if the number of `events` for the visitor ID in the last 24 + * `missing_module` - The request is invalid because it is missing a + required module. - hours (`events.intervals.['24h']`) is higher than 20.000. - additionalProperties: false - properties: - distinctIp: - $ref: '#/components/schemas/VelocityData' - distinctLinkedId: - $ref: '#/components/schemas/VelocityData' - distinctCountry: - $ref: '#/components/schemas/VelocityData' - events: - $ref: '#/components/schemas/VelocityData' - ipEvents: - $ref: '#/components/schemas/VelocityData' - distinctIpByLinkedId: - $ref: '#/components/schemas/VelocityData' - distinctVisitorIdByLinkedId: - $ref: '#/components/schemas/VelocityData' - WebhookDeveloperTools: - type: object - additionalProperties: false - properties: - result: - type: boolean - description: > - `true` if the browser is Chrome with DevTools open or Firefox with - Developer Tools open, `false` otherwise. - WebhookMitMAttack: - type: object - additionalProperties: false - properties: - result: - type: boolean - description: > - * `true` - When requests made from your users' mobile devices to - Fingerprint servers have been intercepted and potentially modified. + * `payload_too_large` - The request payload is too large and cannot be + processed. - * `false` - Otherwise or when the request originated from a browser. + * `service_unavailable` - The service was unable to process the request. - See [MitM Attack - Detection](https://dev.fingerprint.com/docs/smart-signals-overview#mitm-attack-detection) - to learn more about this Smart Signal. - SupplementaryID: + * `ruleset_not_found` - The specified ruleset was not found. It never + existed or it has been deleted. + Error: type: object - additionalProperties: false + required: + - code + - message properties: - visitorId: + code: + $ref: '#/components/schemas/ErrorCode' + message: type: string - description: >- - String of 20 characters that uniquely identifies the visitor's - browser or mobile device. - visitorFound: - type: boolean - description: Attribute represents if a visitor had been identified before. - confidence: - $ref: '#/components/schemas/IdentificationConfidence' - firstSeenAt: - $ref: '#/components/schemas/IdentificationSeenAt' - lastSeenAt: - $ref: '#/components/schemas/IdentificationSeenAt' - WebhookSupplementaryIDs: + ErrorResponse: type: object - description: Other identities that have been established for a given Visitor. required: - - standard - - highRecall + - error properties: - standard: - $ref: '#/components/schemas/SupplementaryID' - highRecall: - $ref: '#/components/schemas/SupplementaryID' - WebhookProximity: + error: + $ref: '#/components/schemas/Error' + EventUpdate: type: object - description: > - Proximity ID represents a fixed geographical zone in a discrete global - grid within which the device is observed. - additionalProperties: false - required: - - id - - precisionRadius - - confidence properties: - id: + linked_id: type: string - description: | - A stable privacy-preserving identifier for a given proximity zone. - precisionRadius: - type: integer - format: int32 - enum: - - 10 - - 25 - - 65 - - 175 - - 450 - - 1200 - - 3300 - - 8500 - - 22500 - description: | - The radius of the proximity zone’s precision level, in meters. - confidence: - type: number - format: float - minimum: 0 - maximum: 1 - description: > - A value between `0` and `1` representing the likelihood that the - true device location lies within the mapped proximity zone. - * Scores closer to `1` indicate high confidence that the location is inside the mapped proximity zone. - * Scores closer to `0` indicate lower confidence, suggesting the true location may fall in an adjacent zone. - Webhook: + description: Linked Id value to assign to the existing event + tags: + type: object + description: >- + A customer-provided value or an object that was sent with the + identification request or updated later. + additionalProperties: true + suspect: + type: boolean + description: Suspect flag indicating observed suspicious or fraudulent event + x-go-force-pointer: true + EventSearch: type: object + description: >- + Contains a list of all identification events matching the specified + search criteria. required: - - requestId - - url - - ip - - time - - timestamp - - sdk + - events properties: - requestId: - type: string - description: Unique identifier of the user's request. - url: - type: string - description: Page URL from which the request was sent. - ip: - type: string - description: IP address of the requesting browser or bot. - environmentId: - type: string - description: Environment ID of the event. - tag: - $ref: '#/components/schemas/Tag' - time: + events: + type: array + items: + $ref: '#/components/schemas/Event' + pagination_key: type: string - format: date-time - x-ogen-time-format: 2006-01-02T15:04:05.999Z07:00 description: >- - Time expressed according to ISO 8601 in UTC format, when the request - from the JS agent was made. We recommend to treat requests that are - older than 2 minutes as malicious. Otherwise, request replay attacks - are possible. - timestamp: + Use this value in the `pagination_key` parameter to request the next + page of search results. + total_hits: type: integer format: int64 - description: Timestamp of the event with millisecond precision in Unix time. - ipLocation: - $ref: '#/components/schemas/DeprecatedGeolocation' - linkedId: - type: string - description: A customer-provided id that was sent with the request. - visitorId: - type: string description: >- - String of 20 characters that uniquely identifies the visitor's - browser or mobile device. - visitorFound: - type: boolean - description: Attribute represents if a visitor had been identified before. - confidence: - $ref: '#/components/schemas/IdentificationConfidence' - firstSeenAt: - $ref: '#/components/schemas/IdentificationSeenAt' - lastSeenAt: - $ref: '#/components/schemas/IdentificationSeenAt' - browserDetails: - $ref: '#/components/schemas/BrowserDetails' - incognito: - type: boolean - description: Flag if user used incognito session. - clientReferrer: - type: string - components: - $ref: '#/components/schemas/RawDeviceAttributes' - bot: - $ref: '#/components/schemas/BotdBot' - userAgent: - type: string - rootApps: - $ref: '#/components/schemas/WebhookRootApps' - emulator: - $ref: '#/components/schemas/WebhookEmulator' - ipInfo: - $ref: '#/components/schemas/WebhookIPInfo' - ipBlocklist: - $ref: '#/components/schemas/WebhookIPBlocklist' - tor: - $ref: '#/components/schemas/WebhookTor' - vpn: - $ref: '#/components/schemas/WebhookVPN' - proxy: - $ref: '#/components/schemas/WebhookProxy' - tampering: - $ref: '#/components/schemas/WebhookTampering' - clonedApp: - $ref: '#/components/schemas/WebhookClonedApp' - factoryReset: - $ref: '#/components/schemas/WebhookFactoryReset' - jailbroken: - $ref: '#/components/schemas/WebhookJailbroken' - frida: - $ref: '#/components/schemas/WebhookFrida' - privacySettings: - $ref: '#/components/schemas/WebhookPrivacySettings' - virtualMachine: - $ref: '#/components/schemas/WebhookVirtualMachine' - rawDeviceAttributes: - $ref: '#/components/schemas/WebhookRawDeviceAttributes' - highActivity: - $ref: '#/components/schemas/WebhookHighActivity' - locationSpoofing: - $ref: '#/components/schemas/WebhookLocationSpoofing' - suspectScore: - $ref: '#/components/schemas/WebhookSuspectScore' - remoteControl: - $ref: '#/components/schemas/WebhookRemoteControl' - velocity: - $ref: '#/components/schemas/WebhookVelocity' - developerTools: - $ref: '#/components/schemas/WebhookDeveloperTools' - mitmAttack: - $ref: '#/components/schemas/WebhookMitMAttack' - replayed: - type: boolean - description: > - `true` if we determined that this payload was replayed, `false` - otherwise. - sdk: - $ref: '#/components/schemas/SDK' - supplementaryIds: - $ref: '#/components/schemas/WebhookSupplementaryIDs' - proximity: - $ref: '#/components/schemas/WebhookProximity' + This value represents the total number of events matching the search + query, up to the limit provided in the `total_hits` query parameter. + Only present if the `total_hits` query parameter was provided. + SearchEventsBot: + type: string + enum: + - all + - good + - bad + - none + description: > + Filter events by the Bot Detection result, specifically: + `all` - events where any kind of bot was detected. + `good` - events where a good bot was detected. + `bad` - events where a bad bot was detected. + `none` - events where no bot was detected. + > Note: When using this parameter, only events with the `bot` property + set to a valid value are returned. Events without a `bot` Smart Signal + result are left out of the response. + SearchEventsVpnConfidence: + type: string + enum: + - high + - medium + - low + description: > + Filter events by VPN Detection result confidence level. + + `high` - events with high VPN Detection confidence. + + `medium` - events with medium VPN Detection confidence. + + `low` - events with low VPN Detection confidence. + + > Note: When using this parameter, only events with the `vpn.confidence` + property set to a valid value are returned. Events without a `vpn` Smart + Signal result are left out of the response. + SearchEventsSdkPlatform: + type: string + enum: + - js + - android + - ios + description: > + Filter events by the SDK Platform associated with the identification + event (`sdk.platform` property) . + + `js` - Javascript agent (Web). + + `ios` - Apple iOS based devices. + + `android` - Android based devices. diff --git a/resources/license_banner.txt b/resources/license_banner.txt index 50d4a0c6..d81a205b 100644 --- a/resources/license_banner.txt +++ b/resources/license_banner.txt @@ -1,2 +1,2 @@ -FingerprintJS Server API Node.js SDK v<%= pkg.version %> - Copyright (c) FingerprintJS, Inc, <%= new Date().getFullYear() %> (https://fingerprint.com) -Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license. \ No newline at end of file +Fingerprint Server Node.js SDK v<%= pkg.version %> - Copyright (c) FingerprintJS, Inc, <%= new Date().getFullYear() %> (https://fingerprint.com) +Licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) license. diff --git a/src/errors/apiErrors.ts b/src/errors/apiErrors.ts index a299fbb0..280d6f6d 100644 --- a/src/errors/apiErrors.ts +++ b/src/errors/apiErrors.ts @@ -1,5 +1,4 @@ -import { ErrorPlainResponse, ErrorResponse } from '../types' -import { getRetryAfter } from './getRetryAfter' +import { ErrorResponse } from '../types' export class SdkError extends Error { constructor( @@ -37,10 +36,6 @@ export class RequestError extends return new RequestError('Unknown error', undefined, response.status, response.statusText, response) } - static fromPlainError(body: ErrorPlainResponse, response: Response) { - return new RequestError(body.error, body, response.status, response.statusText, response) - } - static fromErrorResponse(body: ErrorResponse, response: Response) { return new RequestError(body.error.message, body, response.status, body.error.code, response) } @@ -50,27 +45,7 @@ export class RequestError extends * Error that indicate that the request was throttled. * */ export class TooManyRequestsError extends RequestError<429, ErrorResponse> { - /** - * Number of seconds to wait before retrying the request. - * @remarks - * The value is parsed from the `Retry-After` header of the response. - */ - readonly retryAfter: number = 0 - constructor(body: ErrorResponse, response: Response) { super(body.error.message, body, 429, body.error.code, response) - this.retryAfter = getRetryAfter(response) - } - - static fromPlain(error: ErrorPlainResponse, response: Response) { - return new TooManyRequestsError( - { - error: { - message: error.error, - code: 'TooManyRequests', - }, - }, - response - ) } } diff --git a/src/errors/getRetryAfter.ts b/src/errors/getRetryAfter.ts deleted file mode 100644 index 88ba9183..00000000 --- a/src/errors/getRetryAfter.ts +++ /dev/null @@ -1,4 +0,0 @@ -export function getRetryAfter(response: Response) { - const retryAfter = parseInt(response.headers.get('retry-after') ?? '') - return Number.isNaN(retryAfter) ? 0 : retryAfter -} diff --git a/src/errors/handleErrorResponse.ts b/src/errors/handleErrorResponse.ts index 486bd60a..6ef2a6f5 100644 --- a/src/errors/handleErrorResponse.ts +++ b/src/errors/handleErrorResponse.ts @@ -1,7 +1,6 @@ -import { ErrorPlainResponse, ErrorResponse } from '../types' -import { RequestError, TooManyRequestsError } from './apiErrors' +import { ErrorResponse } from '../types' -function isErrorResponse(value: unknown): value is ErrorResponse { +export function isErrorResponse(value: unknown): value is ErrorResponse { return Boolean( value && typeof value === 'object' && @@ -12,27 +11,3 @@ function isErrorResponse(value: unknown): value is ErrorResponse { 'message' in value.error ) } - -function isPlainErrorResponse(value: unknown): value is ErrorPlainResponse { - return Boolean(value && typeof value === 'object' && 'error' in value && typeof value.error === 'string') -} - -export function handleErrorResponse(json: any, response: Response): never { - if (isErrorResponse(json)) { - if (response.status === 429) { - throw new TooManyRequestsError(json, response) - } - - throw RequestError.fromErrorResponse(json, response) - } - - if (isPlainErrorResponse(json)) { - if (response.status === 429) { - throw TooManyRequestsError.fromPlain(json, response) - } - - throw RequestError.fromPlainError(json, response) - } - - throw RequestError.unknown(response) -} diff --git a/src/utils.ts b/src/errors/toError.ts similarity index 100% rename from src/utils.ts rename to src/errors/toError.ts diff --git a/src/generatedApiTypes.ts b/src/generatedApiTypes.ts index 2f0dc0c3..ef8d18da 100644 --- a/src/generatedApiTypes.ts +++ b/src/generatedApiTypes.ts @@ -1,5 +1,5 @@ export interface paths { - '/events/{request_id}': { + '/events/{event_id}': { parameters: { query?: never header?: never @@ -7,33 +7,35 @@ export interface paths { cookie?: never } /** - * Get event by request ID + * Get an event by event ID * @description Get a detailed analysis of an individual identification event, including Smart Signals. - * Please note that the response includes mobile signals (e.g. `rootApps`) even if the request originated from a non-mobile platform. - * It is highly recommended that you **ignore** the mobile signals for such requests. * - * Use `requestId` as the URL path parameter. This API method is scoped to a request, i.e. all returned information is by `requestId`. + * Use `event_id` as the URL path parameter. This API method is scoped to a request, i.e. all returned information is by `event_id`. * */ get: operations['getEvent'] + put?: never + post?: never + delete?: never + options?: never + head?: never /** - * Update an event with a given request ID - * @description Change information in existing events specified by `requestId` or *flag suspicious events*. + * Update an event + * @description Change information in existing events specified by `event_id` or *flag suspicious events*. + * + * When an event is created, it can be assigned `linked_id` and `tags` submitted through the JS agent parameters. + * This information might not have been available on the client initially, so the Server API permits updating these attributes after the fact. * - * When an event is created, it is assigned `linkedId` and `tag` submitted through the JS agent parameters. This information might not be available on the client so the Server API allows for updating the attributes after the fact. + * **Warning** It's not possible to update events older than one month. * - * **Warning** It's not possible to update events older than 10 days. + * **Warning** Trying to update an event immediately after creation may temporarily result in an + * error (HTTP 409 Conflict. The event is not mutable yet.) as the event is fully propagated across our systems. In such a case, simply retry the request. * */ - put: operations['updateEvent'] - post?: never - delete?: never - options?: never - head?: never - patch?: never + patch: operations['updateEvent'] trace?: never } - '/events/search': { + '/events': { parameters: { query?: never header?: never @@ -41,10 +43,28 @@ export interface paths { cookie?: never } /** - * Get events via search - * @description Search for identification events, including Smart Signals, using multiple filtering criteria. If you don't provide `start` or `end` parameters, the default search range is the last 7 days. + * Search events + * @description ## Search + * + * The `/v4/events` endpoint provides a convenient way to search for past events based on specific parameters. Typical use cases and queries include: + * + * - Searching for events associated with a single `visitor_id` within a time range to get historical behavior of a visitor. + * - Searching for events associated with a single `linked_id` within a time range to get all events associated with your internal account identifier. + * - Excluding all bot traffic from the query (`good` and `bad` bots) + * + * If you don't provide `start` or `end` parameters, the default search range is the **last 7 days**. + * + * ### Filtering events with the `suspect` flag + * + * The `/v4/events` endpoint unlocks a powerful method for fraud protection analytics. The `suspect` flag is exposed in all events where it was previously set by the update API. * - * Please note that events include mobile signals (e.g. `rootApps`) even if the request originated from a non-mobile platform. We recommend you **ignore** mobile signals for such requests. + * You can also apply the `suspect` query parameter as a filter to find all potentially fraudulent activity that you previously marked as `suspect`. This helps identify patterns of fraudulent behavior. + * + * ### Environment scoping + * + * If you use a secret key that is scoped to an environment, you will only get events associated with the same environment. With a workspace-scoped environment, you will get events from all environments. + * + * Smart Signals not activated for your workspace or are not included in the response. * */ get: operations['searchEvents'] @@ -63,32 +83,23 @@ export interface paths { path?: never cookie?: never } - /** - * Get visits by visitor ID - * @description Get a history of visits (identification events) for a specific `visitorId`. Use the `visitorId` as a URL path parameter. - * Only information from the _Identification_ product is returned. - * - * #### Headers - * - * * `Retry-After` — Present in case of `429 Too many requests`. Indicates how long you should wait before making a follow-up request. The value is non-negative decimal integer indicating the seconds to delay after the response is received. - * - */ - get: operations['getVisits'] + get?: never put?: never post?: never /** * Delete data by visitor ID * @description Request deleting all data associated with the specified visitor ID. This API is useful for compliance with privacy regulations. + * * ### Which data is deleted? * - Browser (or device) properties * - Identification requests made from this browser (or device) * * #### Browser (or device) properties * - Represents the data that Fingerprint collected from this specific browser (or device) and everything inferred and derived from it. - * - Upon request to delete, this data is deleted asynchronously (typically within a few minutes) and it will no longer be used to identify this browser (or device) for your [Fingerprint Workspace](https://dev.fingerprint.com/docs/glossary#fingerprint-workspace). + * - Upon request to delete, this data is deleted asynchronously (typically within a few minutes) and it will no longer be used to identify this browser (or device) for your [Fingerprint Workspace](https://docs.fingerprint.com/docs/glossary#fingerprint-workspace). * * #### Identification requests made from this browser (or device) - * - Fingerprint stores the identification requests made from a browser (or device) for up to 30 (or 90) days depending on your plan. To learn more, see [Data Retention](https://dev.fingerprint.com/docs/regions#data-retention). + * - Fingerprint stores the identification requests made from a browser (or device) for up to 30 (or 90) days depending on your plan. To learn more, see [Data Retention](https://docs.fingerprint.com/docs/regions#data-retention). * - Upon request to delete, the identification requests that were made by this browser * - Within the past 10 days are deleted within 24 hrs. * - Outside of 10 days are allowed to purge as per your data retention period. @@ -96,8 +107,7 @@ export interface paths { * ### Corollary * After requesting to delete a visitor ID, * - If the same browser (or device) requests to identify, it will receive a different visitor ID. - * - If you request [`/events` API](https://dev.fingerprint.com/reference/getevent) with a `request_id` that was made outside of the 10 days, you will still receive a valid response. - * - If you request [`/visitors` API](https://dev.fingerprint.com/reference/getvisits) for the deleted visitor ID, the response will include identification requests that were made outside of those 10 days. + * - If you request [`/v4/events` API](https://docs.fingerprint.com/reference/server-api-v4-get-event) with an `event_id` that was made outside of the 10 days, you will still receive a valid response. * * ### Interested? * Please [contact our support team](https://fingerprint.com/support/) to enable it for you. Otherwise, you will receive a 403. @@ -109,460 +119,277 @@ export interface paths { patch?: never trace?: never } - '/related-visitors': { - parameters: { - query?: never - header?: never - path?: never - cookie?: never - } - /** - * Get Related Visitors - * @description Related visitors API lets you link web visits and in-app browser visits that originated from the same mobile device. - * It searches the past 6 months of identification events to find the visitor IDs that belong to the same mobile device as the given visitor ID. - * - * ⚠️ Please note that this API is not enabled by default and is billable separately. ⚠️ - * - * If you would like to use Related visitors API, please contact our [support team](https://fingerprint.com/support). - * To learn more, see [Related visitors API reference](https://dev.fingerprint.com/reference/related-visitors-api). - * - */ - get: operations['getRelatedVisitors'] - put?: never - post?: never - delete?: never - options?: never - head?: never - patch?: never - trace?: never - } - '/webhook': { - parameters: { - query?: never - header?: never - path?: never - cookie?: never - } - get?: never - put?: never - post?: never - delete?: never - options?: never - head?: never - patch?: never - /** - * Dummy path to describe webhook format. - * @description Fake path to describe webhook format. More information about webhooks can be found in the [documentation](https://dev.fingerprint.com/docs/webhooks) - */ - trace: { - parameters: { - query?: never - header?: never - path?: never - cookie?: never - } - requestBody?: { - content: { - 'application/json': components['schemas']['Webhook'] - } - } - responses: { - /** @description Dummy for the schema */ - default: { - headers: { - [name: string]: unknown - } - content?: never - } - } - } - } } export type webhooks = Record export interface components { schemas: { - BrowserDetails: { - browserName: string - browserMajorVersion: string - browserFullVersion: string - os: string - osVersion: string - device: string - userAgent: string - } - GeolocationCity: { - name: string - } - GeolocationCountry: { - code: string - name: string - } - GeolocationContinent: { - code: string - name: string - } - GeolocationSubdivision: { - isoCode: string - name: string - } - GeolocationSubdivisions: components['schemas']['GeolocationSubdivision'][] + /** @description Unique identifier of the user's request. The first portion of the event_id is a unix epoch milliseconds timestamp For example: `1758130560902.8tRtrH` + * */ + EventId: string /** - * @deprecated - * @description This field is **deprecated** and will not return a result for **applications created after January 23rd, 2024**. Please use the [IP Geolocation Smart signal](https://dev.fingerprint.com/docs/smart-signals-overview#ip-geolocation) for geolocation information. + * Format: int64 + * @description Timestamp of the event with millisecond precision in Unix time. */ - DeprecatedGeolocation: { - /** @description The IP address is likely to be within this radius (in km) of the specified location. */ - accuracyRadius?: number - /** Format: double */ - latitude?: number - /** Format: double */ - longitude?: number - postalCode?: string - /** Format: timezone */ - timezone?: string - city?: components['schemas']['GeolocationCity'] - country?: components['schemas']['GeolocationCountry'] - continent?: components['schemas']['GeolocationContinent'] - subdivisions?: components['schemas']['GeolocationSubdivisions'] + Timestamp: number + /** @description A customer-provided id that was sent with the request. */ + LinkedId: string + /** @description Environment Id of the event. For example: `ae_47abaca3db2c7c43` + * */ + EnvironmentId: string + /** @description Field is `true` if you have previously set the `suspect` flag for this event using the [Server API Update event endpoint](https://docs.fingerprint.com/reference/server-api-v4-update-event). */ + Suspect: boolean + Integration: { + /** @description The name of the specific integration, e.g. "fingerprint-pro-react". */ + name?: string + /** @description The version of the specific integration, e.g. "3.11.10". */ + version?: string + subintegration?: { + /** @description The name of the specific subintegration, e.g. "preact". */ + name?: string + /** @description The version of the specific subintegration, e.g. "10.21.0". */ + version?: string + } } - /** @description A customer-provided value or an object that was sent with identification request. */ - Tag: { - [key: string]: unknown + /** @description Contains information about the SDK used to perform the request. */ + SDK: { + /** + * @description Platform of the SDK used for the identification request. + * @enum {string} + */ + platform: 'js' | 'android' | 'ios' | 'unknown' + /** @description Version string of the SDK used for the identification request. For example: `"3.12.1"` + * */ + version: string + integrations?: components['schemas']['Integration'][] } + /** @description `true` if we determined that this payload was replayed, `false` otherwise. + * */ + Replayed: boolean IdentificationConfidence: { /** * Format: double * @description The confidence score is a floating-point number between 0 and 1 that represents the probability of accurate identification. */ score: number - /** @description The revision name of the method used to calculate the Confidence score. This field is only present for customers who opted in to an alternative calculation method. */ - revision?: string + /** @description The version name of the method used to calculate the Confidence score. This field is only present for customers who opted in to an alternative calculation method. */ + version?: string comment?: string } - IdentificationSeenAt: { - /** Format: date-time */ - global: string | null - /** Format: date-time */ - subscription: string | null - } - RawDeviceAttributeError: { - name?: string - message?: string - } - RawDeviceAttribute: { - /** value */ - value?: unknown - error?: components['schemas']['RawDeviceAttributeError'] - } - /** @description It includes 35+ raw browser identification attributes to provide Fingerprint users with even more information than our standard visitor ID provides. This enables Fingerprint users to not have to run our open-source product in conjunction with Fingerprint Pro Plus and Enterprise to get those additional attributes. - * Warning: The raw signals data can change at any moment as we improve the product. We cannot guarantee the internal shape of raw device attributes to be stable, so typical semantic versioning rules do not apply here. Use this data with caution without assuming a specific structure beyond the generic type provided here. - * */ - RawDeviceAttributes: { - [key: string]: components['schemas']['RawDeviceAttribute'] - } - /** @description Contains information about the SDK used to perform the request. */ - SDK: { - /** @description Platform of the SDK. */ - platform: string - /** @description SDK version string. */ - version: string - } Identification: { /** @description String of 20 characters that uniquely identifies the visitor's browser or mobile device. */ - visitorId: string - /** @description Unique identifier of the user's request. */ - requestId: string - browserDetails: components['schemas']['BrowserDetails'] - /** @description Flag if user used incognito session. */ - incognito: boolean - /** @description IP address of the requesting browser or bot. */ - ip: string - /** @description This field is **deprecated** and will not return a result for **applications created after January 23rd, 2024**. Please use the [IP Geolocation Smart signal](https://dev.fingerprint.com/docs/smart-signals-overview#ip-geolocation) for geolocation information. */ - ipLocation?: components['schemas']['DeprecatedGeolocation'] - /** @description A customer-provided id that was sent with the request. */ - linkedId?: string - /** @description Field is `true` if you have previously set the `suspect` flag for this event using the [Server API Update event endpoint](https://dev.fingerprint.com/reference/updateevent). */ - suspect?: boolean + visitor_id: string + confidence?: components['schemas']['IdentificationConfidence'] + /** @description Attribute represents if a visitor had been identified before. */ + visitor_found: boolean /** * Format: int64 - * @description Timestamp of the event with millisecond precision in Unix time. + * @description Unix epoch time milliseconds timestamp indicating the time at which this visitor ID was first seen. example: `1758069706642` - Corresponding to Wed Sep 17 2025 00:41:46 GMT+0000 + * */ - timestamp: number + first_seen_at?: number /** - * Format: date-time - * @description Time expressed according to ISO 8601 in UTC format, when the request from the JS agent was made. We recommend to treat requests that are older than 2 minutes as malicious. Otherwise, request replay attacks are possible. + * Format: int64 + * @description Unix epoch time milliseconds timestamp indicating the time at which this visitor ID was last seen. example: `1758069706642` - Corresponding to Wed Sep 17 2025 00:41:46 GMT+0000 + * */ - time: string - /** @description Page URL from which the request was sent. */ - url: string - /** @description A customer-provided value or an object that was sent with identification request. */ - tag: components['schemas']['Tag'] - confidence?: components['schemas']['IdentificationConfidence'] + last_seen_at?: number + } + /** @description A supplementary browser identifier that prioritizes coverage over precision. The High Recall ID algorithm matches more generously, i.e., this identifier will remain the same even when there are subtle differences between two requests. This algorithm does not create as many new visitor IDs as the standard algorithms do, but there could be an increase in false-positive identification. */ + SupplementaryIDHighRecall: { + /** @description String of 20 characters that uniquely identifies the visitor's browser or mobile device. */ + visitor_id: string /** @description Attribute represents if a visitor had been identified before. */ - visitorFound: boolean - firstSeenAt: components['schemas']['IdentificationSeenAt'] - lastSeenAt: components['schemas']['IdentificationSeenAt'] - /** @description It includes 35+ raw browser identification attributes to provide Fingerprint users with even more information than our standard visitor ID provides. This enables Fingerprint users to not have to run our open-source product in conjunction with Fingerprint Pro Plus and Enterprise to get those additional attributes. - * Warning: The raw signals data can change at any moment as we improve the product. We cannot guarantee the internal shape of raw device attributes to be stable, so typical semantic versioning rules do not apply here. Use this data with caution without assuming a specific structure beyond the generic type provided here. - * */ - components?: components['schemas']['RawDeviceAttributes'] - /** @description `true` if we determined that this payload was replayed, `false` otherwise. - * */ - replayed: boolean - /** @description Contains information about the SDK used to perform the request. */ - sdk?: components['schemas']['SDK'] - /** @description Environment ID associated with the event */ - environmentId?: string + visitor_found: boolean + confidence?: components['schemas']['IdentificationConfidence'] + /** + * Format: int64 + * @description Unix epoch time milliseconds timestamp indicating the time at which this ID was first seen. example: `1758069706642` - Corresponding to Wed Sep 17 2025 00:41:46 GMT+0000 + * + */ + first_seen_at?: number + /** + * Format: int64 + * @description Unix epoch time milliseconds timestamp indicating the time at which this ID was last seen. example: `1758069706642` - Corresponding to Wed Sep 17 2025 00:41:46 GMT+0000 + * + */ + last_seen_at?: number } - /** - * @description Error code: - * * `RequestCannotBeParsed` - the query parameters or JSON payload contains some errors - * that prevented us from parsing it (wrong type/surpassed limits). - * * `TokenRequired` - `Auth-API-Key` header is missing or empty. - * * `TokenNotFound` - no Fingerprint application found for specified secret key. - * * `SubscriptionNotActive` - Fingerprint application is not active. - * * `WrongRegion` - server and application region differ. - * * `FeatureNotEnabled` - this feature (for example, Delete API) is not enabled for your application. - * * `RequestNotFound` - the specified request ID was not found. It never existed, expired, or it has been deleted. - * * `VisitorNotFound` - The specified visitor ID was not found. It never existed or it may have already been deleted. - * * `TooManyRequests` - the limit on secret API key requests per second has been exceeded. - * * `429 Too Many Requests` - the limit on secret API key requests per second has been exceeded. - * * `StateNotReady` - The event specified with request id is - * not ready for updates yet. Try again. - * This error happens in rare cases when update API is called immediately - * after receiving the request id on the client. In case you need to send - * information right away, we recommend using the JS agent API instead. - * * `Failed` - internal server error. - * - * @enum {string} - */ - ErrorCode: - | 'RequestCannotBeParsed' - | 'TokenRequired' - | 'TokenNotFound' - | 'SubscriptionNotActive' - | 'WrongRegion' - | 'FeatureNotEnabled' - | 'RequestNotFound' - | 'VisitorNotFound' - | 'TooManyRequests' - | '429 Too Many Requests' - | 'StateNotReady' - | 'Failed' - Error: { - /** @description Error code: - * * `RequestCannotBeParsed` - the query parameters or JSON payload contains some errors - * that prevented us from parsing it (wrong type/surpassed limits). - * * `TokenRequired` - `Auth-API-Key` header is missing or empty. - * * `TokenNotFound` - no Fingerprint application found for specified secret key. - * * `SubscriptionNotActive` - Fingerprint application is not active. - * * `WrongRegion` - server and application region differ. - * * `FeatureNotEnabled` - this feature (for example, Delete API) is not enabled for your application. - * * `RequestNotFound` - the specified request ID was not found. It never existed, expired, or it has been deleted. - * * `VisitorNotFound` - The specified visitor ID was not found. It never existed or it may have already been deleted. - * * `TooManyRequests` - the limit on secret API key requests per second has been exceeded. - * * `429 Too Many Requests` - the limit on secret API key requests per second has been exceeded. - * * `StateNotReady` - The event specified with request id is - * not ready for updates yet. Try again. - * This error happens in rare cases when update API is called immediately - * after receiving the request id on the client. In case you need to send - * information right away, we recommend using the JS agent API instead. - * * `Failed` - internal server error. - * */ - code: components['schemas']['ErrorCode'] - message: string + /** @description A customer-provided value or an object that was sent with the identification request or updated later. */ + Tags: { + [key: string]: unknown } - ProductIdentification: { - data?: components['schemas']['Identification'] - error?: components['schemas']['Error'] + /** @description Page URL from which the request was sent. For example `https://example.com/` + * */ + Url: string + /** @description Bundle Id of the iOS application integrated with the Fingerprint SDK for the event. For example: `com.foo.app` + * */ + BundleId: string + /** @description Package name of the Android application integrated with the Fingerprint SDK for the event. For example: `com.foo.app` + * */ + PackageName: string + /** @description IP address of the requesting browser or bot. */ + IpAddress: string + /** @description User Agent of the client, for example: `Mozilla/5.0 (Windows NT 6.1; Win64; x64) ....` + * */ + UserAgent: string + /** @description Client Referrer field corresponds to the `document.referrer` field gathered during an identification request. The value is an empty string if the user navigated to the page directly (not through a link, but, for example, by using a bookmark) For example: `https://example.com/blog/my-article` + * */ + ClientReferrer: string + BrowserDetails: { + browser_name: string + browser_major_version: string + browser_full_version: string + os: string + os_version: string + device: string + } + /** @description Proximity ID represents a fixed geographical zone in a discrete global grid within which the device is observed. + * */ + Proximity: { + /** @description A stable privacy-preserving identifier for a given proximity zone. + * */ + id: string + /** + * Format: int32 + * @description The radius of the proximity zone’s precision level, in meters. + * + * @enum {integer} + */ + precision_radius: 10 | 25 | 65 | 175 | 450 | 1200 | 3300 | 8500 | 22500 + /** + * Format: float + * @description A value between `0` and `1` representing the likelihood that the true device location lies within the mapped proximity zone. + * * Scores closer to `1` indicate high confidence that the location is inside the mapped proximity zone. + * * Scores closer to `0` indicate lower confidence, suggesting the true location may fall in an adjacent zone. + * + */ + confidence: number } /** * @description Bot detection result: - * * `notDetected` - the visitor is not a bot - * * `good` - good bot detected, such as Google bot, Baidu Spider, AlexaBot and so on * * `bad` - bad bot detected, such as Selenium, Puppeteer, Playwright, headless browsers, and so on + * * `good` - good bot detected, such as Google bot, Baidu Spider, AlexaBot and so on + * * `not_detected` - the visitor is not a bot * * @enum {string} */ - BotdBotResult: 'notDetected' | 'good' | 'bad' - /** @description Stores bot detection result */ - BotdBot: { - /** @description Bot detection result: - * * `notDetected` - the visitor is not a bot - * * `good` - good bot detected, such as Google bot, Baidu Spider, AlexaBot and so on - * * `bad` - bad bot detected, such as Selenium, Puppeteer, Playwright, headless browsers, and so on - * */ - result: components['schemas']['BotdBotResult'] - type?: string - } - /** @description Contains all the information from Bot Detection product */ - Botd: { - /** @description Stores bot detection result */ - bot: components['schemas']['BotdBot'] - /** @description A customer-provided value or an object that was sent with identification request. */ - meta?: components['schemas']['Tag'] - /** @description A customer-provided id that was sent with the request. */ - linkedId?: string - /** @description Page URL from which the request was sent. */ - url: string - /** @description IP address of the requesting browser or bot. */ - ip: string + BotResult: 'bad' | 'good' | 'not_detected' + /** @description Additional classification of the bot type if detected. + * */ + BotType: string + /** @description Extended bot information. */ + BotInfo: { + /** @description The type and purpose of the bot. */ + category: string + /** @description The organization or company operating the bot. */ + provider: string + /** @description The URL of the bot provider's website. */ + provider_url?: string + /** @description The specific name or identifier of the bot. */ + name: string /** - * Format: date-time - * @description Time in UTC when the request from the JS agent was made. We recommend to treat requests that are older than 2 minutes as malicious. Otherwise, request replay attacks are possible. + * @description The verification status of the bot's identity: + * * `verified` - well-known bot with publicly verifiable identity, directed by the bot provider. + * * `signed` - bot that signs its platform via Web Bot Auth, directed by the bot provider’s customers. + * * `spoofed` - bot that claims a public identity but fails verification. + * * `unknown` - bot that does not publish a verifiable identity. + * + * @enum {string} */ - time: string - userAgent: string - /** @description Unique identifier of the user's request. */ - requestId: string - } - ProductBotd: { - /** @description Contains all the information from Bot Detection product */ - data?: components['schemas']['Botd'] - error?: components['schemas']['Error'] - } - RootApps: { - /** @description Android specific root management apps detection. There are 2 values: - * * `true` - Root Management Apps detected (e.g. Magisk). - * * `false` - No Root Management Apps detected or the client isn't Android. - * */ - result: boolean - } - ProductRootApps: { - data?: components['schemas']['RootApps'] - error?: components['schemas']['Error'] - } - Emulator: { - /** @description Android specific emulator detection. There are 2 values: - * * `true` - Emulated environment detected (e.g. launch inside of AVD). - * * `false` - No signs of emulated environment detected or the client is not Android. - * */ - result: boolean + identity: 'verified' | 'signed' | 'spoofed' | 'unknown' + /** + * @description Confidence level of the bot identification. + * @enum {string} + */ + confidence: 'low' | 'medium' | 'high' } - ProductEmulator: { - data?: components['schemas']['Emulator'] - error?: components['schemas']['Error'] + /** @description Android specific cloned application detection. There are 2 values: * `true` - Presence of app cloners work detected (e.g. fully cloned application found or launch of it inside of a not main working profile detected). * `false` - No signs of cloned application detected or the client is not Android. + * */ + ClonedApp: boolean + /** @description `true` if the browser is Chrome with DevTools open or Firefox with Developer Tools open, `false` otherwise. + * */ + DeveloperTools: boolean + /** @description Android specific emulator detection. There are 2 values: + * * `true` - Emulated environment detected (e.g. launch inside of AVD). + * * `false` - No signs of emulated environment detected or the client is not Android. + * */ + Emulator: boolean + /** + * Format: int64 + * @description The time of the most recent factory reset that happened on the **mobile device** is expressed as Unix epoch time. When a factory reset cannot be detected on the mobile device or when the request is initiated from a browser, this field will correspond to the *epoch* time (i.e 1 Jan 1970 UTC) as a value of 0. See [Factory Reset Detection](https://docs.fingerprint.com/docs/smart-signals-reference#factory-reset-detection) to learn more about this Smart Signal. + * + */ + FactoryReset: number + /** @description [Frida](https://frida.re/docs/) detection for Android and iOS devices. There are 2 values: + * * `true` - Frida detected + * * `false` - No signs of Frida or the client is not a mobile device. + * */ + Frida: boolean + IPBlockList: { + /** @description IP address was part of a known email spam attack (SMTP). */ + email_spam?: boolean + /** @description IP address was part of a known network attack (SSH/HTTPS). */ + attack_source?: boolean + /** @description IP address was part of known TOR network activity. */ + tor_node?: boolean } Geolocation: { /** @description The IP address is likely to be within this radius (in km) of the specified location. */ - accuracyRadius?: number + accuracy_radius?: number /** Format: double */ latitude?: number /** Format: double */ longitude?: number - postalCode?: string + postal_code?: string /** Format: timezone */ timezone?: string - city?: components['schemas']['GeolocationCity'] - country?: components['schemas']['GeolocationCountry'] - continent?: components['schemas']['GeolocationContinent'] - subdivisions?: components['schemas']['GeolocationSubdivisions'] - } - IPInfoASN: { - asn: string - name: string - network: string - } - IPInfoDataCenter: { - result: boolean - name: string + city_name?: string + country_code?: string + country_name?: string + continent_code?: string + continent_name?: string + subdivisions?: { + iso_code: string + name: string + }[] } IPInfoV4: { /** Format: ipv4 */ address: string - geolocation: components['schemas']['Geolocation'] - asn?: components['schemas']['IPInfoASN'] - datacenter?: components['schemas']['IPInfoDataCenter'] + geolocation?: components['schemas']['Geolocation'] + asn?: string + asn_name?: string + asn_network?: string + asn_type?: string + datacenter_result?: boolean + datacenter_name?: string } IPInfoV6: { /** Format: ipv6 */ address: string - geolocation: components['schemas']['Geolocation'] - asn?: components['schemas']['IPInfoASN'] - datacenter?: components['schemas']['IPInfoDataCenter'] + geolocation?: components['schemas']['Geolocation'] + asn?: string + asn_name?: string + asn_network?: string + asn_type?: string + datacenter_result?: boolean + datacenter_name?: string } /** @description Details about the request IP address. Has separate fields for v4 and v6 IP address versions. */ IPInfo: { v4?: components['schemas']['IPInfoV4'] v6?: components['schemas']['IPInfoV6'] } - ProductIPInfo: { - /** @description Details about the request IP address. Has separate fields for v4 and v6 IP address versions. */ - data?: components['schemas']['IPInfo'] - error?: components['schemas']['Error'] - } - IPBlocklistDetails: { - /** @description IP address was part of a known email spam attack (SMTP). */ - emailSpam: boolean - /** @description IP address was part of a known network attack (SSH/HTTPS). */ - attackSource: boolean - } - IPBlocklist: { - /** @description `true` if request IP address is part of any database that we use to search for known malicious actors, `false` otherwise. - * */ - result: boolean - details: components['schemas']['IPBlocklistDetails'] - } - ProductIPBlocklist: { - data?: components['schemas']['IPBlocklist'] - error?: components['schemas']['Error'] - } - Tor: { - /** @description `true` if the request IP address is a known tor exit node, `false` otherwise. - * */ - result: boolean - } - ProductTor: { - data?: components['schemas']['Tor'] - error?: components['schemas']['Error'] - } - /** - * @description A confidence rating for the VPN detection result — "low", "medium", or "high". Depends on the combination of results returned from all VPN detection methods. - * @enum {string} - */ - VPNConfidence: 'low' | 'medium' | 'high' - VPNMethods: { - /** @description The browser timezone doesn't match the timezone inferred from the request IP address. */ - timezoneMismatch: boolean - /** @description Request IP address is owned and used by a public VPN service provider. */ - publicVPN: boolean - /** @description This method applies to mobile devices only. Indicates the result of additional methods used to detect a VPN in mobile devices. */ - auxiliaryMobile: boolean - /** @description The browser runs on a different operating system than the operating system inferred from the request network signature. */ - osMismatch: boolean - /** @description Request IP address belongs to a relay service provider, indicating the use of relay services like [Apple Private relay](https://support.apple.com/en-us/102602) or [Cloudflare Warp](https://developers.cloudflare.com/warp-client/). - * - * * Like VPNs, relay services anonymize the visitor's true IP address. - * * Unlike traditional VPNs, relay services don't let visitors spoof their location by choosing an exit node in a different country. - * - * This field allows you to differentiate VPN users and relay service users in your fraud prevention logic. - * */ - relay: boolean - } - VPN: { - /** @description VPN or other anonymizing service has been used when sending the request. */ - result: boolean - /** @description A confidence rating for the VPN detection result — "low", "medium", or "high". Depends on the combination of results returned from all VPN detection methods. */ - confidence: components['schemas']['VPNConfidence'] - /** @description Local timezone which is used in timezoneMismatch method. */ - originTimezone: string - /** @description Country of the request (only for Android SDK version >= 2.4.0, ISO 3166 format or unknown). */ - originCountry: string - methods: components['schemas']['VPNMethods'] - } - ProductVPN: { - data?: components['schemas']['VPN'] - error?: components['schemas']['Error'] - } + /** @description IP address was used by a public proxy provider or belonged to a known recent residential proxy + * */ + Proxy: boolean /** - * @description Confidence level of the proxy detection. - * If a proxy is not detected, confidence is "high". - * If it's detected, can be "low", "medium", or "high". + * @description Confidence level of the proxy detection. If a proxy is not detected, confidence is "high". If it's detected, can be "low", "medium", or "high". * * @enum {string} */ ProxyConfidence: 'low' | 'medium' | 'high' - /** @description Proxy detection details (present if proxy is detected) */ + /** @description Proxy detection details (present if `proxy` is `true`) */ ProxyDetails: { /** * @description Residential proxies use real user IP addresses to appear as legitimate traffic, @@ -570,747 +397,716 @@ export interface components { * * @enum {string} */ - proxyType: 'residential' | 'data_center' + proxy_type: 'residential' | 'data_center' /** - * Format: date-time - * @description ISO 8601 formatted timestamp in UTC with hourly resolution - * of when this IP was last seen as a proxy when available. + * Format: int64 + * @description Unix millisecond timestamp with hourly resolution of when this IP was last seen as a proxy * */ - lastSeenAt?: string - } | null - Proxy: { - /** @description IP address was used by a public proxy provider or belonged to a known recent residential proxy - * */ - result: boolean - /** @description Confidence level of the proxy detection. - * If a proxy is not detected, confidence is "high". - * If it's detected, can be "low", "medium", or "high". - * */ - confidence: components['schemas']['ProxyConfidence'] - /** @description Proxy detection details (present if proxy is detected) */ - details?: components['schemas']['ProxyDetails'] - } - ProductProxy: { - data?: components['schemas']['Proxy'] - error?: components['schemas']['Error'] - } - Incognito: { - /** @description `true` if we detected incognito mode used in the browser, `false` otherwise. + last_seen_at?: number + /** @description String representing the last proxy service provider detected when this + * IP was synced. An IP can be shared by multiple service providers. * */ - result: boolean + provider?: string } - ProductIncognito: { - data?: components['schemas']['Incognito'] - error?: components['schemas']['Error'] - } - Tampering: { - /** @description Indicates if an identification request from a browser or an Android SDK has been tampered with. Not supported in the iOS SDK, is always `false` for iOS requests. - * * `true` - If the request meets either of the following conditions: - * * Contains anomalous browser or device attributes that could not have been legitimately produced by the JavaScript agent or the Android SDK (see `anomalyScore`). - * * Originated from an anti-detect browser like Incognition (see `antiDetectBrowser`). - * * `false` - If the request is considered genuine or was generated by the iOS SDK. - * */ - result: boolean + /** @description `true` if we detected incognito mode used in the browser, `false` otherwise. + * */ + Incognito: boolean + /** @description iOS specific jailbreak detection. There are 2 values: + * * `true` - Jailbreak detected. + * * `false` - No signs of jailbreak or the client is not iOS. + * */ + Jailbroken: boolean + /** @description Flag indicating whether the request came from a mobile device with location spoofing enabled. */ + LocationSpoofing: boolean + /** @description * `true` - When requests made from your users' mobile devices to Fingerprint servers have been intercepted and potentially modified. + * * `false` - Otherwise or when the request originated from a browser. + * See [MitM Attack Detection](https://docs.fingerprint.com/docs/smart-signals-reference#mitm-attack-detection) to learn more about this Smart Signal. + * */ + MitMAttack: boolean + /** @description `true` if the request is from a privacy aware browser (e.g. Tor) or from a browser in which fingerprinting is blocked. Otherwise `false`. + * */ + PrivacySettings: boolean + /** @description Android specific root management apps detection. There are 2 values: + * * `true` - Root Management Apps detected (e.g. Magisk). + * * `false` - No Root Management Apps detected or the client isn't Android. + * */ + RootApps: boolean + /** @description The ID of the evaluated ruleset. */ + RulesetId: string + /** @description The ID of the rule that matched the identification event. */ + RuleId: string + /** @description The expression of the rule that matched the identification event. */ + RuleExpression: string + /** + * @description Describes the action to take with the request. + * @enum {string} + */ + RuleActionType: 'allow' | 'block' + RuleActionHeaderField: { + /** @description The header field name. */ + name: string + /** @description The value of the header field. */ + value: string + } + /** @description The set of header modifications to apply, in the following order: remove, set, append. */ + RequestHeaderModifications: { + /** @description The list of headers to remove. */ + remove?: string[] + /** @description The list of headers to set, overwriting any existing headers with the same name. */ + set?: components['schemas']['RuleActionHeaderField'][] + /** @description The list of headers to append. */ + append?: components['schemas']['RuleActionHeaderField'][] + } + /** @description Informs the client that the request should be forwarded to the origin with optional request header modifications. */ + EventRuleActionAllow: { + /** @description The ID of the evaluated ruleset. */ + ruleset_id: components['schemas']['RulesetId'] + /** @description The ID of the rule that matched the identification event. */ + rule_id?: components['schemas']['RuleId'] + /** @description The expression of the rule that matched the identification event. */ + rule_expression?: components['schemas']['RuleExpression'] /** - * Format: double - * @description A score that indicates the extent of anomalous data in the request. This field applies to requests originating from **both** browsers and Android SDKs. - * * Values above `0.5` indicate that the request has been tampered with. - * * Values below `0.5` indicate that the request is genuine. - * + * @description discriminator enum property added by openapi-typescript + * @enum {string} */ - anomalyScore: number - /** @description Anti-detect browsers try to evade identification by masking or manipulating their fingerprint to imitate legitimate browser configurations. This field does not apply to requests originating from mobile SDKs. - * * `true` - The browser resembles a known anti-detect browser, for example, Incognition. - * * `false` - The browser does not resemble an anti-detect browser or the request originates from a mobile SDK. - * */ - antiDetectBrowser: boolean - } - ProductTampering: { - data?: components['schemas']['Tampering'] - error?: components['schemas']['Error'] - } - ClonedApp: { - /** @description Android specific cloned application detection. There are 2 values: - * * `true` - Presence of app cloners work detected (e.g. fully cloned application found or launch of it inside of a not main working profile detected). - * * `false` - No signs of cloned application detected or the client is not Android. - * */ - result: boolean - } - ProductClonedApp: { - data?: components['schemas']['ClonedApp'] - error?: components['schemas']['Error'] - } - FactoryReset: { + type: 'allow' + /** @description The set of header modifications to apply, in the following order: remove, set, append. */ + request_header_modifications?: components['schemas']['RequestHeaderModifications'] + } + /** @description A valid HTTP status code. */ + StatusCode: number + /** @description The response body to send to the client. */ + RuleActionBody: string + /** @description Informs the client the request should be blocked using the response described by this rule action. */ + EventRuleActionBlock: { + /** @description The ID of the evaluated ruleset. */ + ruleset_id: components['schemas']['RulesetId'] + /** @description The ID of the rule that matched the identification event. */ + rule_id?: components['schemas']['RuleId'] + /** @description The expression of the rule that matched the identification event. */ + rule_expression?: components['schemas']['RuleExpression'] /** - * Format: date-time - * @description Indicates the time (in UTC) of the most recent factory reset that happened on the **mobile device**. - * When a factory reset cannot be detected on the mobile device or when the request is initiated from a browser, this field will correspond to the *epoch* time (i.e 1 Jan 1970 UTC). - * See [Factory Reset Detection](https://dev.fingerprint.com/docs/smart-signals-overview#factory-reset-detection) to learn more about this Smart Signal. - * + * @description discriminator enum property added by openapi-typescript + * @enum {string} */ - time: string + type: 'block' + /** @description A valid HTTP status code. */ + status_code?: components['schemas']['StatusCode'] + /** @description A list of headers to send. */ + headers?: components['schemas']['RuleActionHeaderField'][] + /** @description The response body to send to the client. */ + body?: components['schemas']['RuleActionBody'] + } + /** @description Describes the action the client should take, according to the rule in the ruleset that matched the event. When getting an event by event ID, the rule_action will only be included when the ruleset_id query parameter is specified. */ + EventRuleAction: components['schemas']['EventRuleActionAllow'] | components['schemas']['EventRuleActionBlock'] + /** @description Suspect Score is an easy way to integrate Smart Signals into your fraud protection work flow. It is a weighted representation of all Smart Signals present in the payload that helps identify suspicious activity. The value range is [0; S] where S is sum of all Smart Signals weights. See more details here: https://docs.fingerprint.com/docs/suspect-score + * */ + SuspectScore: number + /** @description Flag indicating browser tampering was detected. This happens when either: + * * There are inconsistencies in the browser configuration that cross internal tampering thresholds (see `tampering_details.anomaly_score`). + * * The browser signature resembles an "anti-detect" browser specifically designed to evade fingerprinting (see `tampering_details.anti_detect_browser`). + * */ + Tampering: boolean + TamperingDetails: { /** - * Format: int64 - * @description This field is just another representation of the value in the `time` field. - * The time of the most recent factory reset that happened on the **mobile device** is expressed as Unix epoch time. + * Format: double + * @description Confidence score (`0.0 - 1.0`) for tampering detection: + * * Values above `0.5` indicate tampering. + * * Values below `0.5` indicate genuine browsers. * */ - timestamp: number - } - ProductFactoryReset: { - data?: components['schemas']['FactoryReset'] - error?: components['schemas']['Error'] - } - Jailbroken: { - /** @description iOS specific jailbreak detection. There are 2 values: - * * `true` - Jailbreak detected. - * * `false` - No signs of jailbreak or the client is not iOS. + anomaly_score?: number + /** @description True if the identified browser resembles an "anti-detect" browser, such as Incognition, which attempts to evade identification by manipulating its fingerprint. * */ - result: boolean + anti_detect_browser?: boolean } - ProductJailbroken: { - data?: components['schemas']['Jailbroken'] - error?: components['schemas']['Error'] - } - Frida: { - /** @description [Frida](https://frida.re/docs/) detection for Android and iOS devices. There are 2 values: - * * `true` - Frida detected - * * `false` - No signs of Frida or the client is not a mobile device. + /** @description Is absent if the velocity data could not be generated for the visitor Id. + * */ + VelocityData: { + /** @description Count for the last 5 minutes of velocity data, from the time of the event. * */ - result: boolean - } - ProductFrida: { - data?: components['schemas']['Frida'] - error?: components['schemas']['Error'] - } - PrivacySettings: { - /** @description `true` if the request is from a privacy aware browser (e.g. Tor) or from a browser in which fingerprinting is blocked. Otherwise `false`. + '5_minutes': number + /** @description Count for the last 1 hour of velocity data, from the time of the event. * */ - result: boolean - } - ProductPrivacySettings: { - data?: components['schemas']['PrivacySettings'] - error?: components['schemas']['Error'] - } - VirtualMachine: { - /** @description `true` if the request came from a browser running inside a virtual machine (e.g. VMWare), `false` otherwise. + '1_hour': number + /** @description The `24_hours` interval of `distinct_ip`, `distinct_linked_id`, `distinct_country`, `distinct_ip_by_linked_id` and `distinct_visitor_id_by_linked_id` will be omitted if the number of `events` for the visitor Id in the last 24 hours (`events.['24_hours']`) is higher than 20.000. * */ - result: boolean - } - ProductVirtualMachine: { - data?: components['schemas']['VirtualMachine'] - error?: components['schemas']['Error'] + '24_hours'?: number } - ProductRawDeviceAttributes: { - /** @description It includes 35+ raw browser identification attributes to provide Fingerprint users with even more information than our standard visitor ID provides. This enables Fingerprint users to not have to run our open-source product in conjunction with Fingerprint Pro Plus and Enterprise to get those additional attributes. - * Warning: The raw signals data can change at any moment as we improve the product. We cannot guarantee the internal shape of raw device attributes to be stable, so typical semantic versioning rules do not apply here. Use this data with caution without assuming a specific structure beyond the generic type provided here. + /** @description Sums key data points for a specific `visitor_id`, `ip_address` and `linked_id` at three distinct time + * intervals: 5 minutes, 1 hour, and 24 hours as follows: + * + * - Number of distinct IP addresses associated to the visitor Id. + * - Number of distinct linked Ids associated with the visitor Id. + * - Number of distinct countries associated with the visitor Id. + * - Number of identification events associated with the visitor Id. + * - Number of identification events associated with the detected IP address. + * - Number of distinct IP addresses associated with the provided linked Id. + * - Number of distinct visitor Ids associated with the provided linked Id. + * + * The `24h` interval of `distinct_ip`, `distinct_linked_id`, `distinct_country`, + * `distinct_ip_by_linked_id` and `distinct_visitor_id_by_linked_id` will be omitted + * if the number of `events` for the visitor Id in the last 24 + * hours (`events.['24h']`) is higher than 20.000. + * + * All will not necessarily be returned in a response, some may be omitted if the + * associated event does not have the required data, such as a linked_id. + * */ + Velocity: { + /** @description Is absent if the velocity data could not be generated for the visitor Id. * */ - data?: components['schemas']['RawDeviceAttributes'] - error?: components['schemas']['Error'] - } - HighActivity: { - /** @description Flag indicating if the request came from a high-activity visitor. */ - result: boolean - /** - * Format: int64 - * @description Number of requests from the same visitor in the previous day. - */ - dailyRequests?: number - } - ProductHighActivity: { - data?: components['schemas']['HighActivity'] - error?: components['schemas']['Error'] - } - LocationSpoofing: { - /** @description Flag indicating whether the request came from a mobile device with location spoofing enabled. */ - result: boolean - } - ProductLocationSpoofing: { - data?: components['schemas']['LocationSpoofing'] - error?: components['schemas']['Error'] - } - SuspectScore: { - /** @description Suspect Score is an easy way to integrate Smart Signals into your fraud protection work flow. It is a weighted representation of all Smart Signals present in the payload that helps identify suspicious activity. The value range is [0; S] where S is sum of all Smart Signals weights. See more details here: https://dev.fingerprint.com/docs/suspect-score + distinct_ip?: components['schemas']['VelocityData'] + /** @description Is absent if the velocity data could not be generated for the visitor Id. * */ - result: number - } - ProductSuspectScore: { - data?: components['schemas']['SuspectScore'] - error?: components['schemas']['Error'] - } - /** - * @deprecated - * @description This signal is deprecated. - * - */ - RemoteControl: { - /** @description `true` if the request came from a machine being remotely controlled (e.g. TeamViewer), `false` otherwise. + distinct_linked_id?: components['schemas']['VelocityData'] + /** @description Is absent if the velocity data could not be generated for the visitor Id. + * */ + distinct_country?: components['schemas']['VelocityData'] + /** @description Is absent if the velocity data could not be generated for the visitor Id. + * */ + events?: components['schemas']['VelocityData'] + /** @description Is absent if the velocity data could not be generated for the visitor Id. * */ - result: boolean + ip_events?: components['schemas']['VelocityData'] + /** @description Is absent if the velocity data could not be generated for the visitor Id. + * */ + distinct_ip_by_linked_id?: components['schemas']['VelocityData'] + /** @description Is absent if the velocity data could not be generated for the visitor Id. + * */ + distinct_visitor_id_by_linked_id?: components['schemas']['VelocityData'] } + /** @description `true` if the request came from a browser running inside a virtual machine (e.g. VMWare), `false` otherwise. + * */ + VirtualMachine: boolean + /** @description VPN or other anonymizing service has been used when sending the request. + * */ + Vpn: boolean /** - * @deprecated - * @description This product is deprecated. - * + * @description A confidence rating for the VPN detection result — "low", "medium", or "high". Depends on the combination of results returned from all VPN detection methods. + * @enum {string} */ - ProductRemoteControl: { - /** @description This signal is deprecated. + VpnConfidence: 'low' | 'medium' | 'high' + /** @description Local timezone which is used in timezone_mismatch method. + * */ + VpnOriginTimezone: string + /** @description Country of the request (only for Android SDK version >= 2.4.0, ISO 3166 format or unknown). + * */ + VpnOriginCountry: string + VpnMethods: { + /** @description The browser timezone doesn't match the timezone inferred from the request IP address. */ + timezone_mismatch?: boolean + /** @description Request IP address is owned and used by a public VPN service provider. */ + public_vpn?: boolean + /** @description This method applies to mobile devices only. Indicates the result of additional methods used to detect a VPN in mobile devices. */ + auxiliary_mobile?: boolean + /** @description The browser runs on a different operating system than the operating system inferred from the request network signature. */ + os_mismatch?: boolean + /** @description Request IP address belongs to a relay service provider, indicating the use of relay services like [Apple Private relay](https://support.apple.com/en-us/102602) or [Cloudflare Warp](https://developers.cloudflare.com/warp-client/). + * + * * Like VPNs, relay services anonymize the visitor's true IP address. + * * Unlike traditional VPNs, relay services don't let visitors spoof their location by choosing an exit node in a different country. + * + * This field allows you to differentiate VPN users and relay service users in your fraud prevention logic. * */ - data?: components['schemas']['RemoteControl'] - error?: components['schemas']['Error'] + relay?: boolean } - /** @description Is absent if the velocity data could not be generated for the visitor ID. + /** @description Flag indicating if the request came from a high-activity visitor. */ + HighActivity: boolean + /** @description Baseline measurement of canonical fonts rendered on the device. Numeric width metrics, in CSS pixels, for the canonical fonts collected by the agent. * */ - VelocityIntervals: { - '5m': number - '1h': number - /** @description The `24h` interval of `distinctIp`, `distinctLinkedId`, `distinctCountry`, `distinctIpByLinkedId` and `distinctVisitorIdByLinkedId` will be omitted if the number of `events`` for the visitor ID in the last 24 hours (`events.intervals.['24h']`) is higher than 20.000. - * */ - '24h'?: number + FontPreferences: { + /** Format: double */ + default?: number + /** Format: double */ + serif?: number + /** Format: double */ + sans?: number + /** Format: double */ + mono?: number + /** Format: double */ + apple?: number + /** Format: double */ + min?: number + /** Format: double */ + system?: number } - VelocityData: { - /** @description Is absent if the velocity data could not be generated for the visitor ID. - * */ - intervals?: components['schemas']['VelocityIntervals'] + /** @description Bounding box metrics describing how the emoji glyph renders. */ + Emoji: { + /** @description Font family reported by the browser when drawing the emoji. */ + font?: string + /** Format: double */ + width?: number + /** Format: double */ + height?: number + /** Format: double */ + top?: number + /** Format: double */ + bottom?: number + /** Format: double */ + left?: number + /** Format: double */ + right?: number + /** Format: double */ + x?: number + /** Format: double */ + y?: number } - /** @description Sums key data points for a specific `visitorId`, `ipAddress` and `linkedId` at three distinct time - * intervals: 5 minutes, 1 hour, and 24 hours as follows: - * - * - Number of distinct IP addresses associated to the visitor ID. - * - Number of distinct linked IDs associated with the visitor ID. - * - Number of distinct countries associated with the visitor ID. - * - Number of identification events associated with the visitor ID. - * - Number of identification events associated with the detected IP address. - * - Number of distinct IP addresses associated with the provided linked ID. - * - Number of distinct visitor IDs associated with the provided linked ID. + /** + * @description List of fonts detected on the device. + * @example [ + * "Arial Unicode MS", + * "Gill Sans", + * "Helvetica Neue", + * "Menlo" + * ] + */ + Fonts: string[] + /** + * Format: int32 + * @description Rounded amount of RAM (in gigabytes) reported by the browser. + * @example 8 + */ + DeviceMemory: number + /** @description Timezone identifier detected on the client. */ + Timezone: string + /** @description Canvas fingerprint containing winding flag plus geometry/text hashes. */ + Canvas: { + winding?: boolean + /** @description Hash of geometry rendering output or `unsupported` markers. */ + geometry?: string + /** @description Hash of text rendering output or `unsupported` markers. */ + text?: string + } + /** @description Navigator languages reported by the agent including fallbacks. Each inner array represents ordered language preferences reported by different APIs. + * */ + Languages: string[][] + /** @description Hashes of WebGL context attributes and extension support. */ + WebGlExtensions: { + context_attributes?: string + parameters?: string + shader_precisions?: string + extensions?: string + extension_parameters?: string + unsupported_extensions?: string[] + } + /** @description Render and vendor strings reported by the WebGL context. */ + WebGlBasics: { + version?: string + vendor?: string + vendor_unmasked?: string + renderer?: string + renderer_unmasked?: string + shading_language_version?: string + } + /** @description Current screen resolution. */ + ScreenResolution: number[] + /** @description Browser-reported touch capabilities. */ + TouchSupport: { + touch_event?: boolean + touch_start?: boolean + /** Format: int64 */ + max_touch_points?: number + } + /** @description Navigator `oscpu` string. */ + Oscpu: string + /** + * Format: int32 + * @description Integer representing the CPU architecture exposed by the browser. + */ + Architecture: number + /** @description Whether the cookies are enabled in the browser. */ + CookiesEnabled: boolean + /** + * Format: int32 + * @description Number of logical CPU cores reported by the browser. + */ + HardwareConcurrency: number + /** @description Locale derived from the Intl.DateTimeFormat API. Negative values indicate known error states. The negative statuses can be: - "-1": A permanent status for browsers that don't support Intl API. - "-2": A permanent status for browsers that don't supportDateTimeFormat constructor. - "-3": A permanent status for browsers in which DateTimeFormat locale is undefined or null. + * */ + DateTimeLocale: string + /** @description Navigator vendor string. */ + Vendor: string + /** + * Format: int32 + * @description Screen color depth in bits. + */ + ColorDepth: number + /** @description Navigator platform string. */ + Platform: string + /** @description Whether sessionStorage is available. */ + SessionStorage: boolean + /** @description Whether localStorage is available. */ + LocalStorage: boolean + /** + * Format: double + * @description AudioContext fingerprint or negative status when unavailable. The negative statuses can be: - -1: A permanent status for those browsers which are known to always suspend audio context - -2: A permanent status for browsers that don't support the signal - -3: A temporary status that means that an unexpected timeout has happened * - * The `24h` interval of `distinctIp`, `distinctLinkedId`, `distinctCountry`, - * `distinctIpByLinkedId` and `distinctVisitorIdByLinkedId` will be omitted - * if the number of `events` for the visitor ID in the last 24 - * hours (`events.intervals.['24h']`) is higher than 20.000. + */ + Audio: number + /** @description Browser plugins reported by `navigator.plugins`. */ + Plugins: { + name: string + description?: string + mimeTypes?: { + type?: string + suffixes?: string + description?: string + }[] + }[] + /** @description Whether IndexedDB is available. */ + IndexedDb: boolean + /** @description Hash of Math APIs used for entropy collection. */ + Math: string + /** @description A curated subset of raw browser/device attributes that the API surface exposes. Each property contains a value or object with the data for the collected signal. * */ - Velocity: { - distinctIp: components['schemas']['VelocityData'] - distinctLinkedId: components['schemas']['VelocityData'] - distinctCountry: components['schemas']['VelocityData'] - events: components['schemas']['VelocityData'] - ipEvents: components['schemas']['VelocityData'] - distinctIpByLinkedId: components['schemas']['VelocityData'] - distinctVisitorIdByLinkedId: components['schemas']['VelocityData'] - } - ProductVelocity: { - /** @description Sums key data points for a specific `visitorId`, `ipAddress` and `linkedId` at three distinct time - * intervals: 5 minutes, 1 hour, and 24 hours as follows: - * - * - Number of distinct IP addresses associated to the visitor ID. - * - Number of distinct linked IDs associated with the visitor ID. - * - Number of distinct countries associated with the visitor ID. - * - Number of identification events associated with the visitor ID. - * - Number of identification events associated with the detected IP address. - * - Number of distinct IP addresses associated with the provided linked ID. - * - Number of distinct visitor IDs associated with the provided linked ID. - * - * The `24h` interval of `distinctIp`, `distinctLinkedId`, `distinctCountry`, - * `distinctIpByLinkedId` and `distinctVisitorIdByLinkedId` will be omitted - * if the number of `events` for the visitor ID in the last 24 - * hours (`events.intervals.['24h']`) is higher than 20.000. + RawDeviceAttributes: { + /** @description Baseline measurement of canonical fonts rendered on the device. Numeric width metrics, in CSS pixels, for the canonical fonts collected by the agent. * */ - data?: components['schemas']['Velocity'] - error?: components['schemas']['Error'] - } - DeveloperTools: { - /** @description `true` if the browser is Chrome with DevTools open or Firefox with Developer Tools open, `false` otherwise. + font_preferences?: components['schemas']['FontPreferences'] + /** @description Bounding box metrics describing how the emoji glyph renders. */ + emoji?: components['schemas']['Emoji'] + /** @description List of fonts detected on the device. */ + fonts?: components['schemas']['Fonts'] + /** @description Rounded amount of RAM (in gigabytes) reported by the browser. */ + device_memory?: components['schemas']['DeviceMemory'] + /** @description Timezone identifier detected on the client. */ + timezone?: components['schemas']['Timezone'] + /** @description Canvas fingerprint containing winding flag plus geometry/text hashes. */ + canvas?: components['schemas']['Canvas'] + /** @description Navigator languages reported by the agent including fallbacks. Each inner array represents ordered language preferences reported by different APIs. * */ - result: boolean - } - ProductDeveloperTools: { - data?: components['schemas']['DeveloperTools'] - error?: components['schemas']['Error'] - } - MitMAttack: { - /** @description * `true` - When requests made from your users' mobile devices to Fingerprint servers have been intercepted and potentially modified. - * * `false` - Otherwise or when the request originated from a browser. - * See [MitM Attack Detection](https://dev.fingerprint.com/docs/smart-signals-reference#mitm-attack-detection) to learn more about this Smart Signal. + languages?: components['schemas']['Languages'] + /** @description Hashes of WebGL context attributes and extension support. */ + webgl_extensions?: components['schemas']['WebGlExtensions'] + /** @description Render and vendor strings reported by the WebGL context. */ + webgl_basics?: components['schemas']['WebGlBasics'] + /** @description Current screen resolution. */ + screen_resolution?: components['schemas']['ScreenResolution'] + /** @description Browser-reported touch capabilities. */ + touch_support?: components['schemas']['TouchSupport'] + /** @description Navigator `oscpu` string. */ + oscpu?: components['schemas']['Oscpu'] + /** @description Integer representing the CPU architecture exposed by the browser. */ + architecture?: components['schemas']['Architecture'] + /** @description Whether the cookies are enabled in the browser. */ + cookies_enabled?: components['schemas']['CookiesEnabled'] + /** @description Number of logical CPU cores reported by the browser. */ + hardware_concurrency?: components['schemas']['HardwareConcurrency'] + /** @description Locale derived from the Intl.DateTimeFormat API. Negative values indicate known error states. The negative statuses can be: - "-1": A permanent status for browsers that don't support Intl API. - "-2": A permanent status for browsers that don't supportDateTimeFormat constructor. - "-3": A permanent status for browsers in which DateTimeFormat locale is undefined or null. * */ - result: boolean - } - ProductMitMAttack: { - data?: components['schemas']['MitMAttack'] - error?: components['schemas']['Error'] - } - /** @description Proximity ID represents a fixed geographical zone in a discrete global grid within which the device is observed. - * */ - Proximity: { - /** @description A stable privacy-preserving identifier for a given proximity zone. + date_time_locale?: components['schemas']['DateTimeLocale'] + /** @description Navigator vendor string. */ + vendor?: components['schemas']['Vendor'] + /** @description Screen color depth in bits. */ + color_depth?: components['schemas']['ColorDepth'] + /** @description Navigator platform string. */ + platform?: components['schemas']['Platform'] + /** @description Whether sessionStorage is available. */ + session_storage?: components['schemas']['SessionStorage'] + /** @description Whether localStorage is available. */ + local_storage?: components['schemas']['LocalStorage'] + /** @description AudioContext fingerprint or negative status when unavailable. The negative statuses can be: - -1: A permanent status for those browsers which are known to always suspend audio context - -2: A permanent status for browsers that don't support the signal - -3: A temporary status that means that an unexpected timeout has happened * */ - id: string - /** - * Format: int32 - * @description The radius of the proximity zone’s precision level, in meters. - * - * @enum {integer} - */ - precisionRadius: 10 | 25 | 65 | 175 | 450 | 1200 | 3300 | 8500 | 22500 - /** - * Format: float - * @description A value between `0` and `1` representing the likelihood that the true device location lies within the mapped proximity zone. - * * Scores closer to `1` indicate high confidence that the location is inside the mapped proximity zone. - * * Scores closer to `0` indicate lower confidence, suggesting the true location may fall in an adjacent zone. - * - */ - confidence: number - } - ProductProximity: { - /** @description Proximity ID represents a fixed geographical zone in a discrete global grid within which the device is observed. + audio?: components['schemas']['Audio'] + /** @description Browser plugins reported by `navigator.plugins`. */ + plugins?: components['schemas']['Plugins'] + /** @description Whether IndexedDB is available. */ + indexed_db?: components['schemas']['IndexedDb'] + /** @description Hash of Math APIs used for entropy collection. */ + math?: components['schemas']['Math'] + } + /** @description Contains results from Fingerprint Identification and all active Smart Signals. */ + Event: { + /** @description Unique identifier of the user's request. The first portion of the event_id is a unix epoch milliseconds timestamp For example: `1758130560902.8tRtrH` * */ - data?: components['schemas']['Proximity'] - error?: components['schemas']['Error'] - } - /** @description Contains all information about the request identified by `requestId`, depending on the pricing plan (Pro, Pro Plus, Enterprise) */ - Products: { - identification?: components['schemas']['ProductIdentification'] - botd?: components['schemas']['ProductBotd'] - rootApps?: components['schemas']['ProductRootApps'] - emulator?: components['schemas']['ProductEmulator'] - ipInfo?: components['schemas']['ProductIPInfo'] - ipBlocklist?: components['schemas']['ProductIPBlocklist'] - tor?: components['schemas']['ProductTor'] - vpn?: components['schemas']['ProductVPN'] - proxy?: components['schemas']['ProductProxy'] - incognito?: components['schemas']['ProductIncognito'] - tampering?: components['schemas']['ProductTampering'] - clonedApp?: components['schemas']['ProductClonedApp'] - factoryReset?: components['schemas']['ProductFactoryReset'] - jailbroken?: components['schemas']['ProductJailbroken'] - frida?: components['schemas']['ProductFrida'] - privacySettings?: components['schemas']['ProductPrivacySettings'] - virtualMachine?: components['schemas']['ProductVirtualMachine'] - rawDeviceAttributes?: components['schemas']['ProductRawDeviceAttributes'] - highActivity?: components['schemas']['ProductHighActivity'] - locationSpoofing?: components['schemas']['ProductLocationSpoofing'] - suspectScore?: components['schemas']['ProductSuspectScore'] - /** @description This product is deprecated. + event_id: components['schemas']['EventId'] + /** @description Timestamp of the event with millisecond precision in Unix time. */ + timestamp: components['schemas']['Timestamp'] + /** @description A customer-provided id that was sent with the request. */ + linked_id?: components['schemas']['LinkedId'] + /** @description Environment Id of the event. For example: `ae_47abaca3db2c7c43` * */ - remoteControl?: components['schemas']['ProductRemoteControl'] - velocity?: components['schemas']['ProductVelocity'] - developerTools?: components['schemas']['ProductDeveloperTools'] - mitmAttack?: components['schemas']['ProductMitMAttack'] - proximity?: components['schemas']['ProductProximity'] - } - /** @description Contains results from all activated products - Fingerprint Pro, Bot Detection, and others. */ - EventsGetResponse: { - /** @description Contains all information about the request identified by `requestId`, depending on the pricing plan (Pro, Pro Plus, Enterprise) */ - products: components['schemas']['Products'] - } - ErrorResponse: { - error: components['schemas']['Error'] - } - EventsUpdateRequest: { - /** @description LinkedID value to assign to the existing event */ - linkedId?: string - /** @description A customer-provided value or an object that was sent with identification request. */ - tag?: components['schemas']['Tag'] - /** @description Suspect flag indicating observed suspicious or fraudulent event */ - suspect?: boolean - } - /** @description Contains a list of all identification events matching the specified search criteria. */ - SearchEventsResponse: { - events?: { - /** @description Contains all information about the request identified by `requestId`, depending on the pricing plan (Pro, Pro Plus, Enterprise) */ - products: components['schemas']['Products'] - }[] - /** @description Use this value in the `pagination_key` parameter to request the next page of search results. */ - paginationKey?: string - } - Visit: { - /** @description Unique identifier of the user's request. */ - requestId: string - browserDetails: components['schemas']['BrowserDetails'] - /** @description Flag if user used incognito session. */ - incognito: boolean + environment_id?: components['schemas']['EnvironmentId'] + /** @description Field is `true` if you have previously set the `suspect` flag for this event using the [Server API Update event endpoint](https://docs.fingerprint.com/reference/server-api-v4-update-event). */ + suspect?: components['schemas']['Suspect'] + /** @description Contains information about the SDK used to perform the request. */ + sdk?: components['schemas']['SDK'] + /** @description `true` if we determined that this payload was replayed, `false` otherwise. + * */ + replayed?: components['schemas']['Replayed'] + identification?: components['schemas']['Identification'] + /** @description A supplementary browser identifier that prioritizes coverage over precision. The High Recall ID algorithm matches more generously, i.e., this identifier will remain the same even when there are subtle differences between two requests. This algorithm does not create as many new visitor IDs as the standard algorithms do, but there could be an increase in false-positive identification. */ + supplementary_id_high_recall?: components['schemas']['SupplementaryIDHighRecall'] + /** @description A customer-provided value or an object that was sent with the identification request or updated later. */ + tags?: components['schemas']['Tags'] + /** @description Page URL from which the request was sent. For example `https://example.com/` + * */ + url?: components['schemas']['Url'] + /** @description Bundle Id of the iOS application integrated with the Fingerprint SDK for the event. For example: `com.foo.app` + * */ + bundle_id?: components['schemas']['BundleId'] + /** @description Package name of the Android application integrated with the Fingerprint SDK for the event. For example: `com.foo.app` + * */ + package_name?: components['schemas']['PackageName'] /** @description IP address of the requesting browser or bot. */ - ip: string - /** @description This field is **deprecated** and will not return a result for **applications created after January 23rd, 2024**. Please use the [IP Geolocation Smart signal](https://dev.fingerprint.com/docs/smart-signals-overview#ip-geolocation) for geolocation information. */ - ipLocation?: components['schemas']['DeprecatedGeolocation'] - /** @description A customer-provided id that was sent with the request. */ - linkedId?: string - /** - * Format: int64 - * @description Timestamp of the event with millisecond precision in Unix time. - */ - timestamp: number - /** - * Format: date-time - * @description Time expressed according to ISO 8601 in UTC format, when the request from the client agent was made. We recommend to treat requests that are older than 2 minutes as malicious. Otherwise, request replay attacks are possible. - */ - time: string - /** @description Page URL from which the request was sent. */ - url: string - /** @description A customer-provided value or an object that was sent with identification request. */ - tag: components['schemas']['Tag'] - confidence?: components['schemas']['IdentificationConfidence'] - /** @description Attribute represents if a visitor had been identified before. */ - visitorFound: boolean - firstSeenAt: components['schemas']['IdentificationSeenAt'] - lastSeenAt: components['schemas']['IdentificationSeenAt'] - /** @description It includes 35+ raw browser identification attributes to provide Fingerprint users with even more information than our standard visitor ID provides. This enables Fingerprint users to not have to run our open-source product in conjunction with Fingerprint Pro Plus and Enterprise to get those additional attributes. - * Warning: The raw signals data can change at any moment as we improve the product. We cannot guarantee the internal shape of raw device attributes to be stable, so typical semantic versioning rules do not apply here. Use this data with caution without assuming a specific structure beyond the generic type provided here. + ip_address?: components['schemas']['IpAddress'] + /** @description User Agent of the client, for example: `Mozilla/5.0 (Windows NT 6.1; Win64; x64) ....` * */ - components?: components['schemas']['RawDeviceAttributes'] - } - /** @description Pagination-related fields `lastTimestamp` and `paginationKey` are included if you use a pagination parameter like `limit` or `before` and there is more data available on the next page. */ - VisitorsGetResponse: { - visitorId: string - visits: components['schemas']['Visit'][] - /** - * Format: int64 - * @deprecated - * @description ⚠️ Deprecated paging attribute, please use `paginationKey` instead. Timestamp of the last visit in the current page of results. - * - */ - lastTimestamp?: number - /** @description Request ID of the last visit in the current page of results. Use this value in the following request as the `paginationKey` parameter to get the next page of results. */ - paginationKey?: string - } - ErrorPlainResponse: { - error: string - } - RelatedVisitor: { - /** @description Visitor ID of a browser that originates from the same mobile device as the input visitor ID. */ - visitorId: string - } - RelatedVisitorsResponse: { - relatedVisitors: components['schemas']['RelatedVisitor'][] - } - WebhookRootApps: { - /** @description Android specific root management apps detection. There are 2 values: - * * `true` - Root Management Apps detected (e.g. Magisk). - * * `false` - No Root Management Apps detected or the client isn't Android. + user_agent?: components['schemas']['UserAgent'] + /** @description Client Referrer field corresponds to the `document.referrer` field gathered during an identification request. The value is an empty string if the user navigated to the page directly (not through a link, but, for example, by using a bookmark) For example: `https://example.com/blog/my-article` * */ - result?: boolean - } - WebhookEmulator: { + client_referrer?: components['schemas']['ClientReferrer'] + browser_details?: components['schemas']['BrowserDetails'] + /** @description Proximity ID represents a fixed geographical zone in a discrete global grid within which the device is observed. + * */ + proximity?: components['schemas']['Proximity'] + /** @description Bot detection result: + * * `bad` - bad bot detected, such as Selenium, Puppeteer, Playwright, headless browsers, and so on + * * `good` - good bot detected, such as Google bot, Baidu Spider, AlexaBot and so on + * * `not_detected` - the visitor is not a bot + * */ + bot?: components['schemas']['BotResult'] + /** @description Additional classification of the bot type if detected. + * */ + bot_type?: components['schemas']['BotType'] + /** @description Extended bot information. */ + bot_info?: components['schemas']['BotInfo'] + /** @description Android specific cloned application detection. There are 2 values: * `true` - Presence of app cloners work detected (e.g. fully cloned application found or launch of it inside of a not main working profile detected). * `false` - No signs of cloned application detected or the client is not Android. + * */ + cloned_app?: components['schemas']['ClonedApp'] + /** @description `true` if the browser is Chrome with DevTools open or Firefox with Developer Tools open, `false` otherwise. + * */ + developer_tools?: components['schemas']['DeveloperTools'] /** @description Android specific emulator detection. There are 2 values: - * * `true` - Emulated environment detected (e.g. launch inside of AVD). - * * `false` - No signs of emulated environment detected or the client is not Android. + * * `true` - Emulated environment detected (e.g. launch inside of AVD). + * * `false` - No signs of emulated environment detected or the client is not Android. * */ - result?: boolean - } - /** @description Details about the request IP address. Has separate fields for v4 and v6 IP address versions. */ - WebhookIPInfo: { - v4?: components['schemas']['IPInfoV4'] - v6?: components['schemas']['IPInfoV6'] - } - WebhookIPBlocklist: { - /** @description `true` if request IP address is part of any database that we use to search for known malicious actors, `false` otherwise. + emulator?: components['schemas']['Emulator'] + /** @description The time of the most recent factory reset that happened on the **mobile device** is expressed as Unix epoch time. When a factory reset cannot be detected on the mobile device or when the request is initiated from a browser, this field will correspond to the *epoch* time (i.e 1 Jan 1970 UTC) as a value of 0. See [Factory Reset Detection](https://docs.fingerprint.com/docs/smart-signals-reference#factory-reset-detection) to learn more about this Smart Signal. * */ - result?: boolean - details?: components['schemas']['IPBlocklistDetails'] - } - WebhookTor: { - /** @description `true` if the request IP address is a known tor exit node, `false` otherwise. + factory_reset_timestamp?: components['schemas']['FactoryReset'] + /** @description [Frida](https://frida.re/docs/) detection for Android and iOS devices. There are 2 values: + * * `true` - Frida detected + * * `false` - No signs of Frida or the client is not a mobile device. * */ - result?: boolean - } - WebhookVPN: { - /** @description VPN or other anonymizing service has been used when sending the request. */ - result?: boolean - /** @description A confidence rating for the VPN detection result — "low", "medium", or "high". Depends on the combination of results returned from all VPN detection methods. */ - confidence?: components['schemas']['VPNConfidence'] - /** @description Local timezone which is used in timezoneMismatch method. */ - originTimezone?: string - /** @description Country of the request (only for Android SDK version >= 2.4.0, ISO 3166 format or unknown). */ - originCountry?: string - methods?: components['schemas']['VPNMethods'] - } - WebhookProxy: { + frida?: components['schemas']['Frida'] + ip_blocklist?: components['schemas']['IPBlockList'] + /** @description Details about the request IP address. Has separate fields for v4 and v6 IP address versions. */ + ip_info?: components['schemas']['IPInfo'] /** @description IP address was used by a public proxy provider or belonged to a known recent residential proxy * */ - result?: boolean - /** @description Confidence level of the proxy detection. - * If a proxy is not detected, confidence is "high". - * If it's detected, can be "low", "medium", or "high". + proxy?: components['schemas']['Proxy'] + /** @description Confidence level of the proxy detection. If a proxy is not detected, confidence is "high". If it's detected, can be "low", "medium", or "high". * */ - confidence?: components['schemas']['ProxyConfidence'] - /** @description Proxy detection details (present if proxy is detected) */ - details?: components['schemas']['ProxyDetails'] - } - WebhookTampering: { - /** @description Indicates if an identification request from a browser or an Android SDK has been tampered with. Not supported in the iOS SDK, is always `false` for iOS requests. - * * `true` - If the request meets either of the following conditions: - * * Contains anomalous browser or device attributes that could not have been legitimately produced by the JavaScript agent or the Android SDK (see `anomalyScore`). - * * Originated from an anti-detect browser like Incognition (see `antiDetectBrowser`). - * * `false` - If the request is considered genuine or was generated by the iOS SDK. + proxy_confidence?: components['schemas']['ProxyConfidence'] + /** @description Proxy detection details (present if `proxy` is `true`) */ + proxy_details?: components['schemas']['ProxyDetails'] + /** @description `true` if we detected incognito mode used in the browser, `false` otherwise. * */ - result?: boolean - /** - * Format: double - * @description A score that indicates the extent of anomalous data in the request. This field applies to requests originating from **both** browsers and Android SDKs. - * * Values above `0.5` indicate that the request has been tampered with. - * * Values below `0.5` indicate that the request is genuine. - * - */ - anomalyScore?: number - /** @description Anti-detect browsers try to evade identification by masking or manipulating their fingerprint to imitate legitimate browser configurations. This field does not apply to requests originating from mobile SDKs. - * * `true` - The browser resembles a known anti-detect browser, for example, Incognition. - * * `false` - The browser does not resemble an anti-detect browser or the request originates from a mobile SDK. + incognito?: components['schemas']['Incognito'] + /** @description iOS specific jailbreak detection. There are 2 values: + * * `true` - Jailbreak detected. + * * `false` - No signs of jailbreak or the client is not iOS. * */ - antiDetectBrowser?: boolean - } - WebhookClonedApp: { - /** @description Android specific cloned application detection. There are 2 values: - * * `true` - Presence of app cloners work detected (e.g. fully cloned application found or launch of it inside of a not main working profile detected). - * * `false` - No signs of cloned application detected or the client is not Android. + jailbroken?: components['schemas']['Jailbroken'] + /** @description Flag indicating whether the request came from a mobile device with location spoofing enabled. */ + location_spoofing?: components['schemas']['LocationSpoofing'] + /** @description * `true` - When requests made from your users' mobile devices to Fingerprint servers have been intercepted and potentially modified. + * * `false` - Otherwise or when the request originated from a browser. + * See [MitM Attack Detection](https://docs.fingerprint.com/docs/smart-signals-reference#mitm-attack-detection) to learn more about this Smart Signal. * */ - result?: boolean - } - WebhookFactoryReset: { - /** - * Format: date-time - * @description Indicates the time (in UTC) of the most recent factory reset that happened on the **mobile device**. - * When a factory reset cannot be detected on the mobile device or when the request is initiated from a browser, this field will correspond to the *epoch* time (i.e 1 Jan 1970 UTC). - * See [Factory Reset Detection](https://dev.fingerprint.com/docs/smart-signals-overview#factory-reset-detection) to learn more about this Smart Signal. + mitm_attack?: components['schemas']['MitMAttack'] + /** @description `true` if the request is from a privacy aware browser (e.g. Tor) or from a browser in which fingerprinting is blocked. Otherwise `false`. + * */ + privacy_settings?: components['schemas']['PrivacySettings'] + /** @description Android specific root management apps detection. There are 2 values: + * * `true` - Root Management Apps detected (e.g. Magisk). + * * `false` - No Root Management Apps detected or the client isn't Android. + * */ + root_apps?: components['schemas']['RootApps'] + /** @description Describes the action the client should take, according to the rule in the ruleset that matched the event. When getting an event by event ID, the rule_action will only be included when the ruleset_id query parameter is specified. */ + rule_action?: components['schemas']['EventRuleAction'] + /** @description Suspect Score is an easy way to integrate Smart Signals into your fraud protection work flow. It is a weighted representation of all Smart Signals present in the payload that helps identify suspicious activity. The value range is [0; S] where S is sum of all Smart Signals weights. See more details here: https://docs.fingerprint.com/docs/suspect-score + * */ + suspect_score?: components['schemas']['SuspectScore'] + /** @description Flag indicating browser tampering was detected. This happens when either: + * * There are inconsistencies in the browser configuration that cross internal tampering thresholds (see `tampering_details.anomaly_score`). + * * The browser signature resembles an "anti-detect" browser specifically designed to evade fingerprinting (see `tampering_details.anti_detect_browser`). + * */ + tampering?: components['schemas']['Tampering'] + tampering_details?: components['schemas']['TamperingDetails'] + /** @description Sums key data points for a specific `visitor_id`, `ip_address` and `linked_id` at three distinct time + * intervals: 5 minutes, 1 hour, and 24 hours as follows: * - */ - time?: string - /** - * Format: int64 - * @description This field is just another representation of the value in the `time` field. - * The time of the most recent factory reset that happened on the **mobile device** is expressed as Unix epoch time. + * - Number of distinct IP addresses associated to the visitor Id. + * - Number of distinct linked Ids associated with the visitor Id. + * - Number of distinct countries associated with the visitor Id. + * - Number of identification events associated with the visitor Id. + * - Number of identification events associated with the detected IP address. + * - Number of distinct IP addresses associated with the provided linked Id. + * - Number of distinct visitor Ids associated with the provided linked Id. * - */ - timestamp?: number - } - WebhookJailbroken: { - /** @description iOS specific jailbreak detection. There are 2 values: - * * `true` - Jailbreak detected. - * * `false` - No signs of jailbreak or the client is not iOS. + * The `24h` interval of `distinct_ip`, `distinct_linked_id`, `distinct_country`, + * `distinct_ip_by_linked_id` and `distinct_visitor_id_by_linked_id` will be omitted + * if the number of `events` for the visitor Id in the last 24 + * hours (`events.['24h']`) is higher than 20.000. + * + * All will not necessarily be returned in a response, some may be omitted if the + * associated event does not have the required data, such as a linked_id. * */ - result?: boolean - } - WebhookFrida: { - /** @description [Frida](https://frida.re/docs/) detection for Android and iOS devices. There are 2 values: - * * `true` - Frida detected - * * `false` - No signs of Frida or the client is not a mobile device. + velocity?: components['schemas']['Velocity'] + /** @description `true` if the request came from a browser running inside a virtual machine (e.g. VMWare), `false` otherwise. * */ - result?: boolean - } - WebhookPrivacySettings: { - /** @description `true` if the request is from a privacy aware browser (e.g. Tor) or from a browser in which fingerprinting is blocked. Otherwise `false`. + virtual_machine?: components['schemas']['VirtualMachine'] + /** @description VPN or other anonymizing service has been used when sending the request. * */ - result?: boolean - } - WebhookVirtualMachine: { - /** @description `true` if the request came from a browser running inside a virtual machine (e.g. VMWare), `false` otherwise. + vpn?: components['schemas']['Vpn'] + /** @description A confidence rating for the VPN detection result — "low", "medium", or "high". Depends on the combination of results returned from all VPN detection methods. */ + vpn_confidence?: components['schemas']['VpnConfidence'] + /** @description Local timezone which is used in timezone_mismatch method. * */ - result?: boolean - } - /** @description It includes 35+ raw browser identification attributes to provide Fingerprint users with even more information than our standard visitor ID provides. This enables Fingerprint users to not have to run our open-source product in conjunction with Fingerprint Pro Plus and Enterprise to get those additional attributes. - * Warning: The raw signals data can change at any moment as we improve the product. We cannot guarantee the internal shape of raw device attributes to be stable, so typical semantic versioning rules do not apply here. Use this data with caution without assuming a specific structure beyond the generic type provided here. - * */ - WebhookRawDeviceAttributes: { - [key: string]: components['schemas']['RawDeviceAttribute'] - } - WebhookHighActivity: { + vpn_origin_timezone?: components['schemas']['VpnOriginTimezone'] + /** @description Country of the request (only for Android SDK version >= 2.4.0, ISO 3166 format or unknown). + * */ + vpn_origin_country?: components['schemas']['VpnOriginCountry'] + vpn_methods?: components['schemas']['VpnMethods'] /** @description Flag indicating if the request came from a high-activity visitor. */ - result: boolean - /** - * Format: int64 - * @description Number of requests from the same visitor in the previous day. - */ - dailyRequests?: number - } - WebhookLocationSpoofing: { - /** @description Flag indicating whether the request came from a mobile device with location spoofing enabled. */ - result?: boolean - } - WebhookSuspectScore: { - /** @description Suspect Score is an easy way to integrate Smart Signals into your fraud protection work flow. It is a weighted representation of all Smart Signals present in the payload that helps identify suspicious activity. The value range is [0; S] where S is sum of all Smart Signals weights. See more details here: https://dev.fingerprint.com/docs/suspect-score + high_activity_device?: components['schemas']['HighActivity'] + /** @description A curated subset of raw browser/device attributes that the API surface exposes. Each property contains a value or object with the data for the collected signal. * */ - result?: number + raw_device_attributes?: components['schemas']['RawDeviceAttributes'] } /** - * @deprecated - * @description This signal is deprecated. + * @description Error code: + * * `request_cannot_be_parsed` - The query parameters or JSON payload contains some errors + * that prevented us from parsing it (wrong type/surpassed limits). + * * `secret_api_key_required` - secret API key in header is missing or empty. + * * `secret_api_key_not_found` - No Fingerprint workspace found for specified secret API key. + * * `public_api_key_required` - public API key in header is missing or empty. + * * `public_api_key_not_found` - No Fingerprint workspace found for specified public API key. + * * `subscription_not_active` - Fingerprint workspace is not active. + * * `wrong_region` - Server and workspace region differ. + * * `feature_not_enabled` - This feature (for example, Delete API) is not enabled for your workspace. + * * `request_not_found` - The specified event ID was not found. It never existed, expired, or it has been deleted. + * * `visitor_not_found` - The specified visitor ID was not found. It never existed or it may have already been deleted. + * * `too_many_requests` - The limit on secret API key requests per second has been exceeded. + * * `state_not_ready` - The event specified with event ID is + * not ready for updates yet. Try again. + * This error happens in rare cases when update API is called immediately + * after receiving the event ID on the client. In case you need to send + * information right away, we recommend using the JS agent API instead. + * * `failed` - Internal server error. + * * `event_not_found` - The specified event ID was not found. It never existed, expired, or it has been deleted. + * * `missing_module` - The request is invalid because it is missing a required module. + * * `payload_too_large` - The request payload is too large and cannot be processed. + * * `service_unavailable` - The service was unable to process the request. + * * `ruleset_not_found` - The specified ruleset was not found. It never existed or it has been deleted. * + * @enum {string} */ - WebhookRemoteControl: { - /** @description `true` if the request came from a machine being remotely controlled (e.g. TeamViewer), `false` otherwise. - * */ - result?: boolean - } - /** @description Sums key data points for a specific `visitorId`, `ipAddress` and `linkedId` at three distinct time - * intervals: 5 minutes, 1 hour, and 24 hours as follows: - * - * - Number of distinct IP addresses associated to the visitor ID. - * - Number of distinct linked IDs associated with the visitor ID. - * - Number of distinct countries associated with the visitor ID. - * - Number of identification events associated with the visitor ID. - * - Number of identification events associated with the detected IP address. - * - Number of distinct IP addresses associated with the provided linked ID. - * - Number of distinct visitor IDs associated with the provided linked ID. - * - * The `24h` interval of `distinctIp`, `distinctLinkedId`, `distinctCountry`, - * `distinctIpByLinkedId` and `distinctVisitorIdByLinkedId` will be omitted - * if the number of `events` for the visitor ID in the last 24 - * hours (`events.intervals.['24h']`) is higher than 20.000. - * */ - WebhookVelocity: { - distinctIp?: components['schemas']['VelocityData'] - distinctLinkedId?: components['schemas']['VelocityData'] - distinctCountry?: components['schemas']['VelocityData'] - events?: components['schemas']['VelocityData'] - ipEvents?: components['schemas']['VelocityData'] - distinctIpByLinkedId?: components['schemas']['VelocityData'] - distinctVisitorIdByLinkedId?: components['schemas']['VelocityData'] - } - WebhookDeveloperTools: { - /** @description `true` if the browser is Chrome with DevTools open or Firefox with Developer Tools open, `false` otherwise. - * */ - result?: boolean - } - WebhookMitMAttack: { - /** @description * `true` - When requests made from your users' mobile devices to Fingerprint servers have been intercepted and potentially modified. - * * `false` - Otherwise or when the request originated from a browser. - * See [MitM Attack Detection](https://dev.fingerprint.com/docs/smart-signals-overview#mitm-attack-detection) to learn more about this Smart Signal. + ErrorCode: + | 'request_cannot_be_parsed' + | 'secret_api_key_required' + | 'secret_api_key_not_found' + | 'public_api_key_required' + | 'public_api_key_not_found' + | 'subscription_not_active' + | 'wrong_region' + | 'feature_not_enabled' + | 'request_not_found' + | 'visitor_not_found' + | 'too_many_requests' + | 'state_not_ready' + | 'failed' + | 'event_not_found' + | 'missing_module' + | 'payload_too_large' + | 'service_unavailable' + | 'ruleset_not_found' + Error: { + /** @description Error code: + * * `request_cannot_be_parsed` - The query parameters or JSON payload contains some errors + * that prevented us from parsing it (wrong type/surpassed limits). + * * `secret_api_key_required` - secret API key in header is missing or empty. + * * `secret_api_key_not_found` - No Fingerprint workspace found for specified secret API key. + * * `public_api_key_required` - public API key in header is missing or empty. + * * `public_api_key_not_found` - No Fingerprint workspace found for specified public API key. + * * `subscription_not_active` - Fingerprint workspace is not active. + * * `wrong_region` - Server and workspace region differ. + * * `feature_not_enabled` - This feature (for example, Delete API) is not enabled for your workspace. + * * `request_not_found` - The specified event ID was not found. It never existed, expired, or it has been deleted. + * * `visitor_not_found` - The specified visitor ID was not found. It never existed or it may have already been deleted. + * * `too_many_requests` - The limit on secret API key requests per second has been exceeded. + * * `state_not_ready` - The event specified with event ID is + * not ready for updates yet. Try again. + * This error happens in rare cases when update API is called immediately + * after receiving the event ID on the client. In case you need to send + * information right away, we recommend using the JS agent API instead. + * * `failed` - Internal server error. + * * `event_not_found` - The specified event ID was not found. It never existed, expired, or it has been deleted. + * * `missing_module` - The request is invalid because it is missing a required module. + * * `payload_too_large` - The request payload is too large and cannot be processed. + * * `service_unavailable` - The service was unable to process the request. + * * `ruleset_not_found` - The specified ruleset was not found. It never existed or it has been deleted. * */ - result?: boolean - } - SupplementaryID: { - /** @description String of 20 characters that uniquely identifies the visitor's browser or mobile device. */ - visitorId?: string - /** @description Attribute represents if a visitor had been identified before. */ - visitorFound?: boolean - confidence?: components['schemas']['IdentificationConfidence'] - firstSeenAt?: components['schemas']['IdentificationSeenAt'] - lastSeenAt?: components['schemas']['IdentificationSeenAt'] + code: components['schemas']['ErrorCode'] + message: string } - /** @description Other identities that have been established for a given Visitor. */ - WebhookSupplementaryIDs: { - standard: components['schemas']['SupplementaryID'] - highRecall: components['schemas']['SupplementaryID'] + ErrorResponse: { + error: components['schemas']['Error'] } - /** @description Proximity ID represents a fixed geographical zone in a discrete global grid within which the device is observed. - * */ - WebhookProximity: { - /** @description A stable privacy-preserving identifier for a given proximity zone. - * */ - id: string - /** - * Format: int32 - * @description The radius of the proximity zone’s precision level, in meters. - * - * @enum {integer} - */ - precisionRadius: 10 | 25 | 65 | 175 | 450 | 1200 | 3300 | 8500 | 22500 - /** - * Format: float - * @description A value between `0` and `1` representing the likelihood that the true device location lies within the mapped proximity zone. - * * Scores closer to `1` indicate high confidence that the location is inside the mapped proximity zone. - * * Scores closer to `0` indicate lower confidence, suggesting the true location may fall in an adjacent zone. - * - */ - confidence: number + EventUpdate: { + /** @description Linked Id value to assign to the existing event */ + linked_id?: string + /** @description A customer-provided value or an object that was sent with the identification request or updated later. */ + tags?: { + [key: string]: unknown + } + /** @description Suspect flag indicating observed suspicious or fraudulent event */ + suspect?: boolean } - Webhook: { - /** @description Unique identifier of the user's request. */ - requestId: string - /** @description Page URL from which the request was sent. */ - url: string - /** @description IP address of the requesting browser or bot. */ - ip: string - /** @description Environment ID of the event. */ - environmentId?: string - /** @description A customer-provided value or an object that was sent with identification request. */ - tag?: components['schemas']['Tag'] - /** - * Format: date-time - * @description Time expressed according to ISO 8601 in UTC format, when the request from the JS agent was made. We recommend to treat requests that are older than 2 minutes as malicious. Otherwise, request replay attacks are possible. - */ - time: string + /** @description Contains a list of all identification events matching the specified search criteria. */ + EventSearch: { + events: components['schemas']['Event'][] + /** @description Use this value in the `pagination_key` parameter to request the next page of search results. */ + pagination_key?: string /** * Format: int64 - * @description Timestamp of the event with millisecond precision in Unix time. + * @description This value represents the total number of events matching the search query, up to the limit provided in the `total_hits` query parameter. Only present if the `total_hits` query parameter was provided. */ - timestamp: number - /** @description This field is **deprecated** and will not return a result for **applications created after January 23rd, 2024**. Please use the [IP Geolocation Smart signal](https://dev.fingerprint.com/docs/smart-signals-overview#ip-geolocation) for geolocation information. */ - ipLocation?: components['schemas']['DeprecatedGeolocation'] - /** @description A customer-provided id that was sent with the request. */ - linkedId?: string - /** @description String of 20 characters that uniquely identifies the visitor's browser or mobile device. */ - visitorId?: string - /** @description Attribute represents if a visitor had been identified before. */ - visitorFound?: boolean - confidence?: components['schemas']['IdentificationConfidence'] - firstSeenAt?: components['schemas']['IdentificationSeenAt'] - lastSeenAt?: components['schemas']['IdentificationSeenAt'] - browserDetails?: components['schemas']['BrowserDetails'] - /** @description Flag if user used incognito session. */ - incognito?: boolean - clientReferrer?: string - /** @description It includes 35+ raw browser identification attributes to provide Fingerprint users with even more information than our standard visitor ID provides. This enables Fingerprint users to not have to run our open-source product in conjunction with Fingerprint Pro Plus and Enterprise to get those additional attributes. - * Warning: The raw signals data can change at any moment as we improve the product. We cannot guarantee the internal shape of raw device attributes to be stable, so typical semantic versioning rules do not apply here. Use this data with caution without assuming a specific structure beyond the generic type provided here. - * */ - components?: components['schemas']['RawDeviceAttributes'] - /** @description Stores bot detection result */ - bot?: components['schemas']['BotdBot'] - userAgent?: string - rootApps?: components['schemas']['WebhookRootApps'] - emulator?: components['schemas']['WebhookEmulator'] - /** @description Details about the request IP address. Has separate fields for v4 and v6 IP address versions. */ - ipInfo?: components['schemas']['WebhookIPInfo'] - ipBlocklist?: components['schemas']['WebhookIPBlocklist'] - tor?: components['schemas']['WebhookTor'] - vpn?: components['schemas']['WebhookVPN'] - proxy?: components['schemas']['WebhookProxy'] - tampering?: components['schemas']['WebhookTampering'] - clonedApp?: components['schemas']['WebhookClonedApp'] - factoryReset?: components['schemas']['WebhookFactoryReset'] - jailbroken?: components['schemas']['WebhookJailbroken'] - frida?: components['schemas']['WebhookFrida'] - privacySettings?: components['schemas']['WebhookPrivacySettings'] - virtualMachine?: components['schemas']['WebhookVirtualMachine'] - /** @description It includes 35+ raw browser identification attributes to provide Fingerprint users with even more information than our standard visitor ID provides. This enables Fingerprint users to not have to run our open-source product in conjunction with Fingerprint Pro Plus and Enterprise to get those additional attributes. - * Warning: The raw signals data can change at any moment as we improve the product. We cannot guarantee the internal shape of raw device attributes to be stable, so typical semantic versioning rules do not apply here. Use this data with caution without assuming a specific structure beyond the generic type provided here. - * */ - rawDeviceAttributes?: components['schemas']['WebhookRawDeviceAttributes'] - highActivity?: components['schemas']['WebhookHighActivity'] - locationSpoofing?: components['schemas']['WebhookLocationSpoofing'] - suspectScore?: components['schemas']['WebhookSuspectScore'] - /** @description This signal is deprecated. - * */ - remoteControl?: components['schemas']['WebhookRemoteControl'] - /** @description Sums key data points for a specific `visitorId`, `ipAddress` and `linkedId` at three distinct time - * intervals: 5 minutes, 1 hour, and 24 hours as follows: - * - * - Number of distinct IP addresses associated to the visitor ID. - * - Number of distinct linked IDs associated with the visitor ID. - * - Number of distinct countries associated with the visitor ID. - * - Number of identification events associated with the visitor ID. - * - Number of identification events associated with the detected IP address. - * - Number of distinct IP addresses associated with the provided linked ID. - * - Number of distinct visitor IDs associated with the provided linked ID. - * - * The `24h` interval of `distinctIp`, `distinctLinkedId`, `distinctCountry`, - * `distinctIpByLinkedId` and `distinctVisitorIdByLinkedId` will be omitted - * if the number of `events` for the visitor ID in the last 24 - * hours (`events.intervals.['24h']`) is higher than 20.000. - * */ - velocity?: components['schemas']['WebhookVelocity'] - developerTools?: components['schemas']['WebhookDeveloperTools'] - mitmAttack?: components['schemas']['WebhookMitMAttack'] - /** @description `true` if we determined that this payload was replayed, `false` otherwise. - * */ - replayed?: boolean - /** @description Contains information about the SDK used to perform the request. */ - sdk: components['schemas']['SDK'] - /** @description Other identities that have been established for a given Visitor. */ - supplementaryIds?: components['schemas']['WebhookSupplementaryIDs'] - /** @description Proximity ID represents a fixed geographical zone in a discrete global grid within which the device is observed. - * */ - proximity?: components['schemas']['WebhookProximity'] + total_hits?: number } + /** + * @description Filter events by the Bot Detection result, specifically: + * `all` - events where any kind of bot was detected. + * `good` - events where a good bot was detected. + * `bad` - events where a bad bot was detected. + * `none` - events where no bot was detected. + * > Note: When using this parameter, only events with the `bot` property set to a valid value are returned. Events without a `bot` Smart Signal result are left out of the response. + * + * @enum {string} + */ + SearchEventsBot: 'all' | 'good' | 'bad' | 'none' + /** + * @description Filter events by VPN Detection result confidence level. + * `high` - events with high VPN Detection confidence. + * `medium` - events with medium VPN Detection confidence. + * `low` - events with low VPN Detection confidence. + * > Note: When using this parameter, only events with the `vpn.confidence` property set to a valid value are returned. Events without a `vpn` Smart Signal result are left out of the response. + * + * @enum {string} + */ + SearchEventsVpnConfidence: 'high' | 'medium' | 'low' + /** + * @description Filter events by the SDK Platform associated with the identification event (`sdk.platform` property) . + * `js` - Javascript agent (Web). + * `ios` - Apple iOS based devices. + * `android` - Android based devices. + * + * @enum {string} + */ + SearchEventsSdkPlatform: 'js' | 'android' | 'ios' } responses: never parameters: never @@ -1322,11 +1118,16 @@ export type $defs = Record export interface operations { getEvent: { parameters: { - query?: never + query?: { + /** @description The ID of the ruleset to evaluate against the event, producing the action to take for this event. + * The resulting action is returned in the `rule_action` attribute of the response. + * */ + ruleset_id?: string + } header?: never path: { - /** @description The unique [identifier](https://dev.fingerprint.com/reference/get-function#requestid) of each identification request. */ - request_id: string + /** @description The unique [identifier](https://docs.fingerprint.com/reference/js-agent-v4-get-function#event_id) of each identification request (`requestId` can be used in its place). */ + event_id: string } cookie?: never } @@ -1338,7 +1139,16 @@ export interface operations { [name: string]: unknown } content: { - 'application/json': components['schemas']['EventsGetResponse'] + 'application/json': components['schemas']['Event'] + } + } + /** @description Bad request. The event Id provided is not valid. */ + 400: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] } } /** @description Forbidden. Access to this API is denied. */ @@ -1350,7 +1160,7 @@ export interface operations { 'application/json': components['schemas']['ErrorResponse'] } } - /** @description Not found. The request ID cannot be found in this application's data. */ + /** @description Not found. The event Id cannot be found in this workspace's data. */ 404: { headers: { [name: string]: unknown @@ -1359,6 +1169,24 @@ export interface operations { 'application/json': components['schemas']['ErrorResponse'] } } + /** @description Too Many Requests. The request is throttled. */ + 429: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + /** @description Workspace error. */ + 500: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } } } updateEvent: { @@ -1366,14 +1194,14 @@ export interface operations { query?: never header?: never path: { - /** @description The unique event [identifier](https://dev.fingerprint.com/reference/get-function#requestid). */ - request_id: string + /** @description The unique event [identifier](https://docs.fingerprint.com/reference/js-agent-v4-get-function#event_id). */ + event_id: string } cookie?: never } requestBody: { content: { - 'application/json': components['schemas']['EventsUpdateRequest'] + 'application/json': components['schemas']['EventUpdate'] } } responses: { @@ -1402,7 +1230,7 @@ export interface operations { 'application/json': components['schemas']['ErrorResponse'] } } - /** @description Not found. The request ID cannot be found in this application's data. */ + /** @description Not found. The event Id cannot be found in this workspace's data. */ 404: { headers: { [name: string]: unknown @@ -1424,19 +1252,19 @@ export interface operations { } searchEvents: { parameters: { - query: { + query?: { /** @description Limit the number of events returned. * */ - limit: number + limit?: number /** @description Use `pagination_key` to get the next page of results. * - * When more results are available (e.g., you requested up to 200 results for your search using `limit`, but there are more than 200 events total matching your request), the `paginationKey` top-level attribute is added to the response. The key corresponds to the `timestamp` of the last returned event. In the following request, use that value in the `pagination_key` parameter to get the next page of results: + * When more results are available (e.g., you requested up to 100 results for your query using `limit`, but there are more than 100 events total matching your request), the `pagination_key` field is added to the response. The pagination key is an arbitrary string that should not be interpreted in any way and should be passed as-is. In the following request, use that value in the `pagination_key` parameter to get the next page of results: * - * 1. First request, returning most recent 200 events: `GET api-base-url/events/search?limit=200` - * 2. Use `response.paginationKey` to get the next page of results: `GET api-base-url/events/search?limit=200&pagination_key=1740815825085` + * 1. First request, returning most recent 200 events: `GET api-base-url/events?limit=100` + * 2. Use `response.pagination_key` to get the next page of results: `GET api-base-url/events?limit=100&pagination_key=1740815825085` * */ pagination_key?: string - /** @description Unique [visitor identifier](https://dev.fingerprint.com/reference/get-function#visitorid) issued by Fingerprint Pro. + /** @description Unique [visitor identifier](https://docs.fingerprint.com/reference/js-agent-v4-get-function#visitor_id) issued by Fingerprint Identification and all active Smart Signals. * Filter for events matching this `visitor_id`. * */ visitor_id?: string @@ -1445,18 +1273,34 @@ export interface operations { * `good` - events where a good bot was detected. * `bad` - events where a bad bot was detected. * `none` - events where no bot was detected. - * > Note: When using this parameter, only events with the `products.botd.data.bot.result` property set to a valid value are returned. Events without a `products.botd` Smart Signal result are left out of the response. + * > Note: When using this parameter, only events with the `bot` property set to a valid value are returned. Events without a `bot` Smart Signal result are left out of the response. * */ - bot?: 'all' | 'good' | 'bad' | 'none' - /** @description Filter events by IP address range. The range can be as specific as a single IP (/32 for IPv4 or /128 for IPv6) - * All ip_address filters must use CIDR notation, for example, 10.0.0.0/24, 192.168.0.1/32 + bot?: components['schemas']['SearchEventsBot'] + /** @description Filter events by IP address or IP range (if CIDR notation is used). If CIDR notation is not used, a /32 for IPv4 or /128 for IPv6 is assumed. + * Examples of range based queries: 10.0.0.0/24, 192.168.0.1/32 * */ ip_address?: string + /** @description Filter events by the ASN associated with the event's IP address. + * This corresponds to the `ip_info.(v4|v6).asn` property in the response. + * */ + asn?: string /** @description Filter events by your custom identifier. * - * You can use [linked IDs](https://dev.fingerprint.com/reference/get-function#linkedid) to associate identification requests with your own identifier, for example, session ID, purchase ID, or transaction ID. You can then use this `linked_id` parameter to retrieve all events associated with your custom identifier. + * You can use [linked Ids](https://docs.fingerprint.com/reference/js-agent-v4-get-function#linkedid) to associate identification requests with your own identifier, for example, session Id, purchase Id, or transaction Id. You can then use this `linked_id` parameter to retrieve all events associated with your custom identifier. * */ linked_id?: string + /** @description Filter events by the URL (`url` property) associated with the event. + * */ + url?: string + /** @description Filter events by the Bundle ID (iOS) associated with the event. + * */ + bundle_id?: string + /** @description Filter events by the Package Name (Android) associated with the event. + * */ + package_name?: string + /** @description Filter events by the origin field of the event. This is applicable to web events only (e.g., https://example.com) + * */ + origin?: string /** @description Filter events with a timestamp greater than the start time, in Unix time (milliseconds). * */ start?: number @@ -1466,113 +1310,108 @@ export interface operations { /** @description Sort events in reverse timestamp order. * */ reverse?: boolean - /** @description Filter events previously tagged as suspicious via the [Update API](https://dev.fingerprint.com/reference/updateevent). + /** @description Filter events previously tagged as suspicious via the [Update API](https://docs.fingerprint.com/reference/server-api-v4-update-event). * > Note: When using this parameter, only events with the `suspect` property explicitly set to `true` or `false` are returned. Events with undefined `suspect` property are left out of the response. * */ suspect?: boolean /** @description Filter events by VPN Detection result. - * > Note: When using this parameter, only events with the `products.vpn.data.result` property set to `true` or `false` are returned. Events without a `products.vpn` Smart Signal result are left out of the response. + * > Note: When using this parameter, only events with the `vpn` property set to `true` or `false` are returned. Events without a `vpn` Smart Signal result are left out of the response. * */ vpn?: boolean /** @description Filter events by Virtual Machine Detection result. - * > Note: When using this parameter, only events with the `products.virtualMachine.data.result` property set to `true` or `false` are returned. Events without a `products.virtualMachine` Smart Signal result are left out of the response. + * > Note: When using this parameter, only events with the `virtual_machine` property set to `true` or `false` are returned. Events without a `virtual_machine` Smart Signal result are left out of the response. * */ virtual_machine?: boolean - /** @description Filter events by Tampering Detection result. - * > Note: When using this parameter, only events with the `products.tampering.data.result` property set to `true` or `false` are returned. Events without a `products.tampering` Smart Signal result are left out of the response. + /** @description Filter events by Browser Tampering Detection result. + * > Note: When using this parameter, only events with the `tampering.result` property set to `true` or `false` are returned. Events without a `tampering` Smart Signal result are left out of the response. * */ tampering?: boolean /** @description Filter events by Anti-detect Browser Detection result. - * > Note: When using this parameter, only events with the `products.tampering.data.antiDetectBrowser` property set to `true` or `false` are returned. Events without a `products.tampering` Smart Signal result are left out of the response. + * > Note: When using this parameter, only events with the `tampering.anti_detect_browser` property set to `true` or `false` are returned. Events without a `tampering` Smart Signal result are left out of the response. * */ anti_detect_browser?: boolean /** @description Filter events by Browser Incognito Detection result. - * > Note: When using this parameter, only events with the `products.incognito.data.result` property set to `true` or `false` are returned. Events without a `products.incognito` Smart Signal result are left out of the response. + * > Note: When using this parameter, only events with the `incognito` property set to `true` or `false` are returned. Events without an `incognito` Smart Signal result are left out of the response. * */ incognito?: boolean /** @description Filter events by Privacy Settings Detection result. - * > Note: When using this parameter, only events with the `products.privacySettings.data.result` property set to `true` or `false` are returned. Events without a `products.privacySettings` Smart Signal result are left out of the response. + * > Note: When using this parameter, only events with the `privacy_settings` property set to `true` or `false` are returned. Events without a `privacy_settings` Smart Signal result are left out of the response. * */ privacy_settings?: boolean /** @description Filter events by Jailbroken Device Detection result. - * > Note: When using this parameter, only events with the `products.jailbroken.data.result` property set to `true` or `false` are returned. Events without a `products.jailbroken` Smart Signal result are left out of the response. + * > Note: When using this parameter, only events with the `jailbroken` property set to `true` or `false` are returned. Events without a `jailbroken` Smart Signal result are left out of the response. * */ jailbroken?: boolean /** @description Filter events by Frida Detection result. - * > Note: When using this parameter, only events with the `products.frida.data.result` property set to `true` or `false` are returned. Events without a `products.frida` Smart Signal result are left out of the response. + * > Note: When using this parameter, only events with the `frida` property set to `true` or `false` are returned. Events without a `frida` Smart Signal result are left out of the response. * */ frida?: boolean /** @description Filter events by Factory Reset Detection result. - * > Note: When using this parameter, only events with the `products.factoryReset.data.result` property set to `true` or `false` are returned. Events without a `products.factoryReset` Smart Signal result are left out of the response. + * > Note: When using this parameter, only events with a `factory_reset` time. Events without a `factory_reset` Smart Signal result are left out of the response. * */ factory_reset?: boolean /** @description Filter events by Cloned App Detection result. - * > Note: When using this parameter, only events with the `products.clonedApp.data.result` property set to `true` or `false` are returned. Events without a `products.clonedApp` Smart Signal result are left out of the response. + * > Note: When using this parameter, only events with the `cloned_app` property set to `true` or `false` are returned. Events without a `cloned_app` Smart Signal result are left out of the response. * */ cloned_app?: boolean /** @description Filter events by Android Emulator Detection result. - * > Note: When using this parameter, only events with the `products.emulator.data.result` property set to `true` or `false` are returned. Events without a `products.emulator` Smart Signal result are left out of the response. + * > Note: When using this parameter, only events with the `emulator` property set to `true` or `false` are returned. Events without an `emulator` Smart Signal result are left out of the response. * */ emulator?: boolean /** @description Filter events by Rooted Device Detection result. - * > Note: When using this parameter, only events with the `products.rootApps.data.result` property set to `true` or `false` are returned. Events without a `products.rootApps` Smart Signal result are left out of the response. + * > Note: When using this parameter, only events with the `root_apps` property set to `true` or `false` are returned. Events without a `root_apps` Smart Signal result are left out of the response. * */ root_apps?: boolean /** @description Filter events by VPN Detection result confidence level. * `high` - events with high VPN Detection confidence. * `medium` - events with medium VPN Detection confidence. * `low` - events with low VPN Detection confidence. - * > Note: When using this parameter, only events with the `products.vpn.data.confidence` property set to a valid value are returned. Events without a `products.vpn` Smart Signal result are left out of the response. + * > Note: When using this parameter, only events with the `vpn.confidence` property set to a valid value are returned. Events without a `vpn` Smart Signal result are left out of the response. * */ - vpn_confidence?: 'high' | 'medium' | 'low' + vpn_confidence?: components['schemas']['SearchEventsVpnConfidence'] /** @description Filter events with Suspect Score result above a provided minimum threshold. - * > Note: When using this parameter, only events where the `products.suspectScore.data.result` property set to a value exceeding your threshold are returned. Events without a `products.suspectScore` Smart Signal result are left out of the response. + * > Note: When using this parameter, only events where the `suspect_score` property set to a value exceeding your threshold are returned. Events without a `suspect_score` Smart Signal result are left out of the response. * */ min_suspect_score?: number - /** @description Filter events by IP Blocklist Detection result. - * > Note: When using this parameter, only events with the `products.ipBlocklist.data.result` property set to `true` or `false` are returned. Events without a `products.ipBlocklist` Smart Signal result are left out of the response. - * */ - ip_blocklist?: boolean - /** @description Filter events by Datacenter Detection result. - * > Note: When using this parameter, only events with the `products.ipInfo.data.v4.datacenter.result` or `products.ipInfo.data.v6.datacenter.result` property set to `true` or `false` are returned. Events without a `products.ipInfo` Smart Signal result are left out of the response. - * */ - datacenter?: boolean /** @description Filter events by Developer Tools detection result. - * > Note: When using this parameter, only events with the `products.developerTools.data.result` property set to `true` or `false` are returned. Events without a `products.developerTools` Smart Signal result are left out of the response. + * > Note: When using this parameter, only events with the `developer_tools` property set to `true` or `false` are returned. Events without a `developer_tools` Smart Signal result are left out of the response. * */ developer_tools?: boolean /** @description Filter events by Location Spoofing detection result. - * > Note: When using this parameter, only events with the `products.locationSpoofing.data.result` property set to `true` or `false` are returned. Events without a `products.locationSpoofing` Smart Signal result are left out of the response. + * > Note: When using this parameter, only events with the `location_spoofing` property set to `true` or `false` are returned. Events without a `location_spoofing` Smart Signal result are left out of the response. * */ location_spoofing?: boolean /** @description Filter events by MITM (Man-in-the-Middle) Attack detection result. - * > Note: When using this parameter, only events with the `products.mitmAttack.data.result` property set to `true` or `false` are returned. Events without a `products.mitmAttack` Smart Signal result are left out of the response. + * > Note: When using this parameter, only events with the `mitm_attack` property set to `true` or `false` are returned. Events without a `mitm_attack` Smart Signal result are left out of the response. * */ mitm_attack?: boolean /** @description Filter events by Proxy detection result. - * > Note: When using this parameter, only events with the `products.proxy.data.result` property set to `true` or `false` are returned. Events without a `products.proxy` Smart Signal result are left out of the response. + * > Note: When using this parameter, only events with the `proxy` property set to `true` or `false` are returned. Events without a `proxy` Smart Signal result are left out of the response. * */ proxy?: boolean - /** @description Filter events by a specific SDK version associated with the identification event. Example: `3.11.14` + /** @description Filter events by a specific SDK version associated with the identification event (`sdk.version` property). Example: `3.11.14` * */ sdk_version?: string - /** @description Filter events by the SDK Platform associated with the identification event. - * `js` - JavaScript agent (Web). + /** @description Filter events by the SDK Platform associated with the identification event (`sdk.platform` property) . + * `js` - Javascript agent (Web). * `ios` - Apple iOS based devices. * `android` - Android based devices. * */ - sdk_platform?: 'js' | 'android' | 'ios' - /** @description Filter for events by providing one or more environment IDs. + sdk_platform?: components['schemas']['SearchEventsSdkPlatform'] + /** @description Filter for events by providing one or more environment IDs (`environment_id` property). * */ environment?: string[] /** @description Filter events by the most precise Proximity ID provided by default. - * > Note: When using this parameter, only events with the `products.proximity.id` property matching the provided ID are returned. Events without a `products.proximity` result are left out of the response. + * > Note: When using this parameter, only events with the `proximity.id` property matching the provided ID are returned. Events without a `proximity` result are left out of the response. * */ proximity_id?: string - /** @description Filter events by Proximity Radius. - * > Note: When using this parameter, only events with the `products.proximity.precisionRadius` property set to a valid value are returned. Events without a `products.proximity` result are left out of the response. + /** @description When set, the response will include a `total_hits` property with a count of total query matches across all pages, up to the specified limit. + * */ + total_hits?: number + /** @description Filter events by Tor Node detection result. + * > Note: When using this parameter, only events with the `tor_node` property set to `true` or `false` are returned. Events without a `tor_node` detection result are left out of the response. * */ - proximity_precision_radius?: 10 | 25 | 65 | 175 | 450 | 1200 | 3300 | 8500 | 22500 + tor_node?: boolean } header?: never path?: never @@ -1586,7 +1425,7 @@ export interface operations { [name: string]: unknown } content: { - 'application/json': components['schemas']['SearchEventsResponse'] + 'application/json': components['schemas']['EventSearch'] } } /** @description Bad request. One or more supplied search parameters are invalid, or a required parameter is missing. */ @@ -1607,89 +1446,13 @@ export interface operations { 'application/json': components['schemas']['ErrorResponse'] } } - } - } - getVisits: { - parameters: { - query?: { - /** @description Filter visits by `requestId`. - * - * Every identification request has a unique identifier associated with it called `requestId`. This identifier is returned to the client in the identification [result](https://dev.fingerprint.com/reference/get-function#requestid). When you filter visits by `requestId`, only one visit will be returned. - * */ - request_id?: string - /** @description Filter visits by your custom identifier. - * - * You can use [`linkedId`](https://dev.fingerprint.com/reference/get-function#linkedid) to associate identification requests with your own identifier, for example: session ID, purchase ID, or transaction ID. You can then use this `linked_id` parameter to retrieve all events associated with your custom identifier. - * */ - linked_id?: string - /** @description Limit scanned results. - * - * For performance reasons, the API first scans some number of events before filtering them. Use `limit` to specify how many events are scanned before they are filtered by `requestId` or `linkedId`. Results are always returned sorted by the timestamp (most recent first). - * By default, the most recent 100 visits are scanned, the maximum is 500. - * */ - limit?: number - /** @description Use `paginationKey` to get the next page of results. - * - * When more results are available (e.g., you requested 200 results using `limit` parameter, but a total of 600 results are available), the `paginationKey` top-level attribute is added to the response. The key corresponds to the `requestId` of the last returned event. In the following request, use that value in the `paginationKey` parameter to get the next page of results: - * - * 1. First request, returning most recent 200 events: `GET api-base-url/visitors/:visitorId?limit=200` - * 2. Use `response.paginationKey` to get the next page of results: `GET api-base-url/visitors/:visitorId?limit=200&paginationKey=1683900801733.Ogvu1j` - * - * Pagination happens during scanning and before filtering, so you can get less visits than the `limit` you specified with more available on the next page. When there are no more results available for scanning, the `paginationKey` attribute is not returned. - * */ - paginationKey?: string - /** - * @deprecated - * @description ⚠️ Deprecated pagination method, please use `paginationKey` instead. Timestamp (in milliseconds since epoch) used to paginate results. - * - */ - before?: number - } - header?: never - path: { - /** @description Unique [visitor identifier](https://dev.fingerprint.com/reference/get-function#visitorid) issued by Fingerprint Pro. */ - visitor_id: string - } - cookie?: never - } - requestBody?: never - responses: { - /** @description OK. */ - 200: { - headers: { - [name: string]: unknown - } - content: { - 'application/json': components['schemas']['VisitorsGetResponse'] - } - } - /** @description Bad request. The visitor ID or query parameters are missing or in the wrong format. */ - 400: { - headers: { - [name: string]: unknown - } - content: { - 'application/json': components['schemas']['ErrorPlainResponse'] - } - } - /** @description Forbidden. Access to this API is denied. */ - 403: { - headers: { - [name: string]: unknown - } - content: { - 'application/json': components['schemas']['ErrorPlainResponse'] - } - } - /** @description Too Many Requests. The request is throttled. */ - 429: { + /** @description Workspace error. */ + 500: { headers: { - /** @description Indicates how many seconds you should wait before attempting the next request. */ - 'Retry-After'?: number [name: string]: unknown } content: { - 'application/json': components['schemas']['ErrorPlainResponse'] + 'application/json': components['schemas']['ErrorResponse'] } } } @@ -1699,7 +1462,7 @@ export interface operations { query?: never header?: never path: { - /** @description The [visitor ID](https://dev.fingerprint.com/reference/get-function#visitorid) you want to delete. */ + /** @description The [visitor ID](https://docs.fingerprint.com/reference/js-agent-v4-get-function#visitor_id) you want to delete. */ visitor_id: string } cookie?: never @@ -1731,66 +1494,7 @@ export interface operations { 'application/json': components['schemas']['ErrorResponse'] } } - /** @description Not found. The visitor ID cannot be found in this application's data. */ - 404: { - headers: { - [name: string]: unknown - } - content: { - 'application/json': components['schemas']['ErrorResponse'] - } - } - /** @description Too Many Requests. The request is throttled. */ - 429: { - headers: { - [name: string]: unknown - } - content: { - 'application/json': components['schemas']['ErrorResponse'] - } - } - } - } - getRelatedVisitors: { - parameters: { - query: { - /** @description The [visitor ID](https://dev.fingerprint.com/reference/get-function#visitorid) for which you want to find the other visitor IDs that originated from the same mobile device. */ - visitor_id: string - } - header?: never - path?: never - cookie?: never - } - requestBody?: never - responses: { - /** @description OK. */ - 200: { - headers: { - [name: string]: unknown - } - content: { - 'application/json': components['schemas']['RelatedVisitorsResponse'] - } - } - /** @description Bad request. The visitor ID parameter is missing or in the wrong format. */ - 400: { - headers: { - [name: string]: unknown - } - content: { - 'application/json': components['schemas']['ErrorResponse'] - } - } - /** @description Forbidden. Access to this API is denied. */ - 403: { - headers: { - [name: string]: unknown - } - content: { - 'application/json': components['schemas']['ErrorResponse'] - } - } - /** @description Not found. The visitor ID cannot be found in this application's data. */ + /** @description Not found. The visitor ID cannot be found in this workspace's data. */ 404: { headers: { [name: string]: unknown diff --git a/src/index.ts b/src/index.ts index c9ce0fa7..d775d44a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,9 +1,16 @@ -export * from './urlUtils' export * from './serverApiClient' -export * from './types' +export { + ErrorResponse, + Event, + EventRuleAction, + EventUpdate, + GetEventOptions, + Options, + Region, + SearchEventsFilter, + SearchEventsResponse, +} from './types' export * from './sealedResults' export * from './errors/unsealError' export * from './webhook' export * from './errors/apiErrors' -export * from './errors/unsealError' -export * from './errors/getRetryAfter' diff --git a/src/responseUtils.ts b/src/responseUtils.ts deleted file mode 100644 index f5c59035..00000000 --- a/src/responseUtils.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { SdkError } from './errors/apiErrors' -import { toError } from './utils' - -export async function copyResponseJson(response: Response) { - try { - return await response.clone().json() - } catch (e) { - throw new SdkError('Failed to parse JSON response', response, toError(e)) - } -} diff --git a/src/sealedResults.ts b/src/sealedResults.ts index 86f86aa3..60ae04e2 100644 --- a/src/sealedResults.ts +++ b/src/sealedResults.ts @@ -1,7 +1,7 @@ import { createDecipheriv } from 'crypto' import { inflateRaw } from 'zlib' import { promisify } from 'util' -import { EventsGetResponse } from './types' +import { Event } from './types' import { UnsealAggregateError, UnsealError } from './errors/unsealError' import { Buffer } from 'buffer' @@ -18,14 +18,14 @@ export interface DecryptionKey { const SEALED_HEADER = Buffer.from([0x9e, 0x85, 0xdc, 0xed]) -function isEventResponse(data: unknown): data is EventsGetResponse { - return Boolean(data && typeof data === 'object' && 'products' in data) +function isEventResponse(data: unknown): data is Event { + return Boolean(data && typeof data === 'object' && 'event_id' in data && 'timestamp' in data) } /** * @private * */ -export function parseEventsResponse(unsealed: string): EventsGetResponse { +export function parseEventsResponse(unsealed: string): Event { const json = JSON.parse(unsealed) if (!isEventResponse(json)) { @@ -42,10 +42,7 @@ export function parseEventsResponse(unsealed: string): EventsGetResponse { * @throws UnsealAggregateError * @throws Error */ -export async function unsealEventsResponse( - sealedData: Buffer, - decryptionKeys: DecryptionKey[] -): Promise { +export async function unsealEventsResponse(sealedData: Buffer, decryptionKeys: DecryptionKey[]): Promise { const unsealed = await unseal(sealedData, decryptionKeys) return parseEventsResponse(unsealed) diff --git a/src/serverApiClient.ts b/src/serverApiClient.ts index 3d947d1d..5799c742 100644 --- a/src/serverApiClient.ts +++ b/src/serverApiClient.ts @@ -1,31 +1,27 @@ -import { getRequestPath } from './urlUtils' +import { AllowedMethod, getRequestPath, GetRequestPathOptions, SuccessJsonOrVoid } from './urlUtils' import { - AuthenticationMode, - EventsGetResponse, - EventsUpdateRequest, + Event, + EventUpdate, FingerprintApi, + GetEventOptions, Options, Region, - RelatedVisitorsFilter, - RelatedVisitorsResponse, SearchEventsFilter, SearchEventsResponse, - VisitorHistoryFilter, - VisitorsResponse, } from './types' -import { copyResponseJson } from './responseUtils' -import { handleErrorResponse } from './errors/handleErrorResponse' +import { paths } from './generatedApiTypes' +import { RequestError, SdkError, TooManyRequestsError } from './errors/apiErrors' +import { isErrorResponse } from './errors/handleErrorResponse' +import { toError } from './errors/toError' -export class FingerprintJsServerApiClient implements FingerprintApi { +export class FingerprintServerApiClient implements FingerprintApi { public readonly region: Region public readonly apiKey: string - public readonly authenticationMode: AuthenticationMode - protected readonly fetch: typeof fetch - protected static readonly DEFAULT_RETRY_AFTER = 1 + private readonly defaultHeaders: Record /** * FingerprintJS server API client used to fetch data from FingerprintJS @@ -41,24 +37,30 @@ export class FingerprintJsServerApiClient implements FingerprintApi { // region or authentication mode to be specified as a string or an enum value. // The resulting JS from using the enum value or the string is identical. this.region = (options.region as Region) ?? Region.Global - this.authenticationMode = (options.authenticationMode as AuthenticationMode) ?? AuthenticationMode.AuthHeader // Default auth mode is AuthHeader this.apiKey = options.apiKey this.fetch = options.fetch ?? fetch + + this.defaultHeaders = { + Authorization: `Bearer ${this.apiKey}`, + ...options.defaultHeaders, + } } /** * Retrieves a specific identification event with the information from each activated product — Identification and all active [Smart signals](https://dev.fingerprint.com/docs/smart-signals-overview). * - * @param requestId - identifier of the event + * @param eventId - identifier of the event + * @param {object|undefined} options - Optional `getEvent` operation options + * @param {string|undefined} options.ruleset_id - Optional ruleset ID to evaluate against the event * - * @returns {Promise} - promise with event response. For more information, see the [Server API documentation](https://dev.fingerprint.com/reference/getevent). + * @returns {Promise} - promise with event response. For more information, see the [Server API documentation](https://dev.fingerprint.com/reference/getevent). * * @example - * ```javascript + * ```javascript Handling an event * client - * .getEvent('') - * .then((result) => console.log(result)) + * .getEvent('') + * .then((event) => console.log(event)) * .catch((error) => { * if (error instanceof RequestError) { * console.log(error.statusCode, error.message) @@ -67,58 +69,59 @@ export class FingerprintJsServerApiClient implements FingerprintApi { * } * }) * ``` + * + * @example Handling an event with rule_action + * ```javascript + * client + * .getEvent('', { ruleset_id: '' }) + * .then((event) => { + * const ruleAction = event.rule_action + * if (ruleAction?.type === 'block') { + * console.log('Blocked by rule:', ruleAction.rule_id, ruleAction.status_code) + * } + * }) + * .catch((error) => { + * if (error instanceof RequestError) { + * console.log(error.statusCode, error.message) + * } + * }) + * ``` * */ - public async getEvent(requestId: string): Promise { - if (!requestId) { - throw new TypeError('requestId is not set') + public async getEvent(eventId: string, options?: GetEventOptions): Promise { + if (!eventId) { + throw new TypeError('eventId is not set') } - const url = getRequestPath({ - path: '/events/{request_id}', - region: this.region, - apiKey: this.getQueryApiKey(), - pathParams: [requestId], + return this.callApi({ + path: '/events/{event_id}', + pathParams: [eventId], method: 'get', + queryParams: options, }) - - const headers = this.getHeaders() - - const response = await this.fetch(url, { - method: 'GET', - headers, - }) - - const jsonResponse = await copyResponseJson(response) - - if (response.status === 200) { - return jsonResponse as EventsGetResponse - } - - handleErrorResponse(jsonResponse, response) } /** - * Update an event with a given request ID - * @description Change information in existing events specified by `requestId` or *flag suspicious events*. + * Update an event with a given event ID + * @description Change information in existing events specified by `eventId` or *flag suspicious events*. * * When an event is created, it is assigned `linkedId` and `tag` submitted through the JS agent parameters. This information might not be available on the client so the Server API allows for updating the attributes after the fact. * - * **Warning** It's not possible to update events older than 10 days. + * **Warning** It's not possible to update events older than one month. * * @param body - Data to update the event with. - * @param requestId The unique event [identifier](https://dev.fingerprint.com/docs/js-agent#requestid). + * @param eventId The unique event [identifier](https://docs.fingerprint.com/reference/js-agent-v4-get-function#event_id). * * @return {Promise} * * @example * ```javascript * const body = { - * linkedId: 'linked_id', + * linked_id: 'linked_id', * suspect: false, * } * * client - * .updateEvent(body, '') + * .updateEvent(body, '') * .then(() => { * // Event was successfully updated * }) @@ -135,37 +138,21 @@ export class FingerprintJsServerApiClient implements FingerprintApi { * }) * ``` */ - public async updateEvent(body: EventsUpdateRequest, requestId: string): Promise { + public async updateEvent(body: EventUpdate, eventId: string): Promise { if (!body) { throw new TypeError('body is not set') } - if (!requestId) { - throw new TypeError('requestId is not set') + if (!eventId) { + throw new TypeError('eventId is not set') } - const url = getRequestPath({ - path: '/events/{request_id}', - region: this.region, - apiKey: this.getQueryApiKey(), - pathParams: [requestId], - method: 'put', - }) - const headers = this.getHeaders() - - const response = await this.fetch(url, { - method: 'PUT', - headers, + return this.callApi({ + path: '/events/{event_id}', + pathParams: [eventId], + method: 'patch', body: JSON.stringify(body), }) - - if (response.status === 200) { - return - } - - const jsonResponse = await copyResponseJson(response) - - handleErrorResponse(jsonResponse, response) } /** @@ -190,48 +177,20 @@ export class FingerprintJsServerApiClient implements FingerprintApi { * console.log(error.statusCode, error.message) * // Access raw response in error * console.log(error.response) - * - * if(error instanceof TooManyRequestsError) { - * retryLater(error.retryAfter) // Needs to be implemented on your side - * } * } * }) * ``` */ public async deleteVisitorData(visitorId: string): Promise { if (!visitorId) { - throw TypeError('VisitorId is not set') + throw new TypeError('visitorId is not set') } - const url = getRequestPath({ + return this.callApi({ path: '/visitors/{visitor_id}', - region: this.region, - apiKey: this.getQueryApiKey(), pathParams: [visitorId], method: 'delete', }) - - const headers = this.getHeaders() - - const response = await this.fetch(url, { - method: 'DELETE', - headers, - }) - - if (response.status === 200) { - return - } - - const jsonResponse = await copyResponseJson(response) - - handleErrorResponse(jsonResponse, response) - } - - /** - * @deprecated Please use {@link FingerprintJsServerApiClient.getVisits} instead - * */ - public async getVisitorHistory(visitorId: string, filter?: VisitorHistoryFilter): Promise { - return this.getVisits(visitorId, filter) } /** @@ -245,176 +204,118 @@ export class FingerprintJsServerApiClient implements FingerprintApi { * * @param {SearchEventsFilter} filter - Events filter * @param {number} filter.limit - Limit the number of events returned. Must be greater than 0. - * @param {string|undefined} filter.visitorId - Unique [visitor identifier](https://dev.fingerprint.com/reference/get-function#visitorid) issued by Fingerprint Pro. Filter for events matching this `visitor_id`. - * @param {string|undefined} filter.bot - Filter events by the bot detection result, specifically: - * - events where any kind of bot was detected. - * - events where a good bot was detected. - * - events where a bad bot was detected. - * - events where no bot was detected. + * @param {string|undefined} filter.pagination_key - Use `pagination_key` to get the next page of results. + * @param {string|undefined} filter.visitor_id - Unique [visitor identifier](https://dev.fingerprint.com/reference/get-function#visitorid) issued by Fingerprint Identification. Filter for events matching this `visitor_id`. + * @param {string|undefined} filter.bot - Filter events by the bot detection result, specifically: + * - events where any kind of bot was detected. + * - events where a good bot was detected. + * - events where a bad bot was detected. + * - events where no bot was detected. * - * Allowed values: `all`, `good`, `bad`, `none`. + * Allowed values: `all`, `good`, `bad`, `none`. * @param {string|undefined} filter.ip_address - Filter events by IP address range. The range can be as specific as a * single IP (/32 for IPv4 or /128 for IPv6). * All ip_address filters must use CIDR notation, for example, * 10.0.0.0/24, 192.168.0.1/32 - * @param {string|undefined} filter.linked_id - Filter events by your custom identifier. - * - * + * @param {string|undefined} filter.linked_id - Filter events by your custom identifier. * You can use [linked IDs](https://dev.fingerprint.com/reference/get-function#linkedid) to * associate identification requests with your own identifier, for * example, session ID, purchase ID, or transaction ID. You can then * use this `linked_id` parameter to retrieve all events associated * with your custom identifier. + * @param {string|undefined} filter.url - Filter events by the URL (`url` property) associated with the event. + * @param {string|undefined} filter.origin - Filter events by the origin field of the event. Origin could be the website domain or mobile app bundle ID (eg: com.foo.bar) * @param {number|undefined} filter.start - Filter events with a timestamp greater than the start time, in Unix time (milliseconds). * @param {number|undefined} filter.end - Filter events with a timestamp smaller than the end time, in Unix time (milliseconds). * @param {boolean|undefined} filter.reverse - Sort events in reverse timestamp order. * @param {boolean|undefined} filter.suspect - Filter events previously tagged as suspicious via the [Update API](https://dev.fingerprint.com/reference/updateevent). + * @param {boolean|undefined} filter.vpn - Filter events by VPN Detection result. + * @param {boolean|undefined} filter.virtual_machine - Filter events by Virtual Machine Detection result. + * @param {boolean|undefined} filter.tampering - Filter events by Browser Tampering Detection result. + * @param {boolean|undefined} filter.anti_detect_browser - Filter events by Anti-detect Browser Detection result. + * @param {boolean|undefined} filter.incognito - Filter events by Browser Incognito Detection result. + * @param {boolean|undefined} filter.privacy_settings - Filter events by Privacy Settings Detection result. + * @param {boolean|undefined} filter.jailbroken - Filter events by Jailbroken Device Detection result. + * @param {boolean|undefined} filter.frida - Filter events by Frida Detection result. + * @param {boolean|undefined} filter.factory_reset - Filter events by Factory Reset Detection result. + * @param {boolean|undefined} filter.cloned_app - Filter events by Cloned App Detection result. + * @param {boolean|undefined} filter.emulator - Filter events by Android Emulator Detection result. + * @param {boolean|undefined} filter.root_apps - Filter events by Rooted Device Detection result. + * @param {'high'|'medium'|'low'|undefined} filter.vpn_confidence - Filter events by VPN Detection result confidence level. + * @param {number|undefined} filter.min_suspect_score - Filter events with Suspect Score result above a provided minimum threshold. + * @param {boolean|undefined} filter.developer_tools - Filter events by Developer Tools detection result. + * @param {boolean|undefined} filter.location_spoofing - Filter events by Location Spoofing detection result. + * @param {boolean|undefined} filter.mitm_attack - Filter events by MITM (Man-in-the-Middle) Attack detection result. + * @param {boolean|undefined} filter.proxy - Filter events by Proxy detection result. + * @param {string|undefined} filter.sdk_version - Filter events by a specific SDK version associated with the identification event (`sdk.version` property). + * @param {string|undefined} filter.sdk_platform - Filter events by the SDK Platform associated with the identification event (`sdk.platform` property). + * @param {string[]|undefined} filter.environment - Filter for events by providing one or more environment IDs (`environment_id` property). * */ async searchEvents(filter: SearchEventsFilter): Promise { - const url = getRequestPath({ - path: '/events/search', - region: this.region, - apiKey: this.getQueryApiKey(), + return this.callApi({ + path: '/events', method: 'get', queryParams: filter, }) - const headers = this.getHeaders() - const response = await this.fetch(url, { - method: 'GET', - headers, - }) - - const jsonResponse = await copyResponseJson(response) - - if (response.status === 200) { - return jsonResponse as SearchEventsResponse - } - - handleErrorResponse(jsonResponse, response) } - /** - * Retrieves event history for the specific visitor using the given filter, returns a promise with visitor history response. - * - * @param {string} visitorId - Identifier of the visitor - * @param {VisitorHistoryFilter} filter - Visitor history filter - * @param {string} filter.limit - limit scanned results - * @param {string} filter.request_id - filter visits by `requestId`. - * @param {string} filter.linked_id - filter visits by your custom identifier. - * @param {string} filter.paginationKey - use `paginationKey` to get the next page of results. When more results are available (e.g., you requested 200 results using `limit` parameter, but a total of 600 results are available), the `paginationKey` top-level attribute is added to the response. The key corresponds to the `requestId` of the last returned event. In the following request, use that value in the `paginationKey` parameter to get the next page of results: - * - * 1. First request, returning most recent 200 events: `GET api-base-url/visitors/:visitorId?limit=200` - * 2. Use `response.paginationKey` to get the next page of results: `GET api-base-url/visitors/:visitorId?limit=200&paginationKey=1683900801733.Ogvu1j` - * - * Pagination happens during scanning and before filtering, so you can get less visits than the `limit` you specified with more available on the next page. When there are no more results available for scanning, the `paginationKey` attribute is not returned. - * @example - * ```javascript - * client - * .getVisits('', { limit: 1 }) - * .then((visitorHistory) => { - * console.log(visitorHistory) - * }) - * .catch((error) => { - * if (error instanceof RequestError) { - * console.log(error.statusCode, error.message) - * // Access raw response in error - * console.log(error.response) - * - * if(error instanceof TooManyRequestsError) { - * retryLater(error.retryAfter) // Needs to be implemented on your side - * } - * } - * }) - * ``` - */ - public async getVisits(visitorId: string, filter?: VisitorHistoryFilter): Promise { - if (!visitorId) { - throw TypeError('VisitorId is not set') - } - + private async callApi>( + options: GetRequestPathOptions & { headers?: Record; body?: BodyInit } + ): Promise> { const url = getRequestPath({ - path: '/visitors/{visitor_id}', + ...options, region: this.region, - apiKey: this.getQueryApiKey(), - pathParams: [visitorId], - method: 'get', - queryParams: filter, }) - const headers = this.getHeaders() - const response = await this.fetch(url, { - method: 'GET', - headers, - }) - - const jsonResponse = await copyResponseJson(response) - - if (response.status === 200) { - return jsonResponse as VisitorsResponse + let response: Response + try { + response = await this.fetch(url, { + method: options.method.toUpperCase(), + headers: { + ...this.defaultHeaders, + ...options.headers, + }, + body: options.body, + }) + } catch (e) { + throw new SdkError('Network or fetch error', undefined, e as Error) } - handleErrorResponse(jsonResponse, response) - } + const contentType = response.headers.get('content-type') ?? '' + const isJson = contentType.includes('application/json') - /** - * Related visitors API lets you link web visits and in-app browser visits that originated from the same mobile device. - * It searches the past 6 months of identification events to find the visitor IDs that belong to the same mobile device as the given visitor ID. - * ⚠️ Please note that this API is not enabled by default and is billable separately. ⚠️ - * If you would like to use Related visitors API, please contact our [support team](https://fingerprint.com/support). - * To learn more, see [Related visitors API reference](https://dev.fingerprint.com/reference/related-visitors-api). - * - * @param {RelatedVisitorsFilter} filter - Related visitors filter - * @param {string} filter.visitorId - The [visitor ID](https://dev.fingerprint.com/docs/js-agent#visitorid) for which you want to find the other visitor IDs that originated from the same mobile device. - * - * @example - * ```javascript - * client - * .getRelatedVisitors({ visitor_id: '' }) - * .then((relatedVisits) => { - * console.log(relatedVisits) - * }) - * .catch((error) => { - * if (error instanceof RequestError) { - * console.log(error.statusCode, error.message) - * // Access raw response in error - * console.log(error.response) - * - * if(error instanceof TooManyRequestsError) { - * retryLater(error.retryAfter) // Needs to be implemented on your side - * } - * } - * }) - * ``` - */ - async getRelatedVisitors(filter: RelatedVisitorsFilter): Promise { - const url = getRequestPath({ - path: '/related-visitors', - region: this.region, - apiKey: this.getQueryApiKey(), - method: 'get', - queryParams: filter, - }) - const headers = this.getHeaders() + if (response.ok) { + const hasNoBody = response.status === 204 || response.headers.get('content-length') === '0' - const response = await this.fetch(url, { - method: 'GET', - headers, - }) + if (hasNoBody) { + return undefined as SuccessJsonOrVoid + } - const jsonResponse = await copyResponseJson(response) + if (!isJson) { + throw new SdkError('Expected JSON response but received non-JSON content type', response) + } - if (response.status === 200) { - return jsonResponse as RelatedVisitorsResponse + let data + try { + data = await response.clone().json() + } catch (e) { + throw new SdkError('Failed to parse JSON response', response, toError(e)) + } + return data as SuccessJsonOrVoid } - handleErrorResponse(jsonResponse, response) - } - - private getHeaders() { - return this.authenticationMode === AuthenticationMode.AuthHeader ? { 'Auth-API-Key': this.apiKey } : undefined - } - - private getQueryApiKey() { - return this.authenticationMode === AuthenticationMode.QueryParameter ? this.apiKey : undefined + let errPayload + try { + errPayload = await response.clone().json() + } catch (e) { + throw new SdkError('Failed to parse JSON response', response, toError(e)) + } + if (isErrorResponse(errPayload)) { + if (response.status === 429) { + throw new TooManyRequestsError(errPayload, response) + } + throw new RequestError(errPayload.error.message, errPayload, response.status, errPayload.error.code, response) + } + throw RequestError.unknown(response) } } diff --git a/src/types.ts b/src/types.ts index f684676f..79a04f80 100644 --- a/src/types.ts +++ b/src/types.ts @@ -6,11 +6,6 @@ export enum Region { Global = 'Global', } -export enum AuthenticationMode { - AuthHeader = 'AuthHeader', - QueryParameter = 'QueryParameter', -} - /** * Options for FingerprintJS server API client */ @@ -23,46 +18,36 @@ export interface Options { * Region of the FingerprintJS service server */ region?: Region | `${Region}` - /** - * Authentication mode - * Optional, default value is AuthHeader - */ - authenticationMode?: AuthenticationMode | `${AuthenticationMode}` /** * Optional fetch implementation * */ fetch?: typeof fetch + + /** + * Optional default headers + */ + defaultHeaders?: Record } +export type ErrorResponse = components['schemas']['ErrorResponse'] + /** * More info: https://dev.fingerprintjs.com/docs/server-api#query-parameters */ -export type VisitorHistoryFilter = paths['/visitors/{visitor_id}']['get']['parameters']['query'] - -export type ErrorPlainResponse = components['schemas']['ErrorPlainResponse'] -export type ErrorResponse = components['schemas']['ErrorResponse'] - -export type SearchEventsFilter = paths['/events/search']['get']['parameters']['query'] -export type SearchEventsResponse = paths['/events/search']['get']['responses']['200']['content']['application/json'] +export type SearchEventsFilter = paths['/events']['get']['parameters']['query'] +export type SearchEventsResponse = components['schemas']['EventSearch'] /** - * More info: https://dev.fingerprintjs.com/docs/server-api#response + * More info: https://dev.fingerprint.com/reference/server-api-v4-get-event */ -export type VisitorsResponse = paths['/visitors/{visitor_id}']['get']['responses']['200']['content']['application/json'] +export type Event = components['schemas']['Event'] -export type EventsGetResponse = paths['/events/{request_id}']['get']['responses']['200']['content']['application/json'] +export type GetEventOptions = paths['/events/{event_id}']['get']['parameters']['query'] -export type RelatedVisitorsResponse = - paths['/related-visitors']['get']['responses']['200']['content']['application/json'] -export type RelatedVisitorsFilter = paths['/related-visitors']['get']['parameters']['query'] - -/** - * More info: https://dev.fingerprintjs.com/docs/webhooks#identification-webhook-object-format - */ -export type Webhook = components['schemas']['Webhook'] +export type EventUpdate = components['schemas']['EventUpdate'] -export type EventsUpdateRequest = components['schemas']['EventsUpdateRequest'] +export type EventRuleAction = components['schemas']['EventRuleAction'] // Extract just the `path` parameters as a tuple of strings type ExtractPathParamStrings = Path extends { parameters: { path: infer P } } @@ -90,7 +75,7 @@ type ExtractResponse = Path extends { responses: { 200: { content: { 'appl type ApiMethodArgs = [ // If method has body, extract it as first parameter ...(ExtractRequestBody extends never ? [] : [body: ExtractRequestBody]), - // Next are path params, e.g. for path "/events/{request_id}" it will be one string parameter, + // Next are path params, e.g. for path "/events/{event_id}" it will be one string parameter, ...ExtractPathParamStrings, // Last parameter will be the query params, if any ...(ExtractQueryParams extends never ? [] : [params: ExtractQueryParams]), @@ -101,5 +86,5 @@ type ApiMethod = ( ) => Promise> export type FingerprintApi = { - [Path in keyof operations]: ApiMethod + [Operation in keyof operations]: ApiMethod } diff --git a/src/urlUtils.ts b/src/urlUtils.ts index 5c07864c..6cb3848e 100644 --- a/src/urlUtils.ts +++ b/src/urlUtils.ts @@ -2,6 +2,8 @@ import { ExtractQueryParams, Region } from './types' import { version } from '../package.json' import { paths } from './generatedApiTypes' +const apiVersion = 'v4' + const euRegionUrl = 'https://eu.api.fpjs.io/' const apRegionUrl = 'https://ap.api.fpjs.io/' const globalRegionUrl = 'https://api.fpjs.io/' @@ -17,36 +19,26 @@ export function getIntegrationInfo() { return `fingerprint-pro-server-node-sdk/${version}` } -function isEmptyValue(value: any): boolean { - return value === undefined || value === null -} - function serializeQueryStringParams(params: QueryStringParameters): string { const entries: [string, string][] = [] for (const [key, value] of Object.entries(params)) { - // Use the helper for the main value - if (isEmptyValue(value)) { + if (value == null) { continue } if (Array.isArray(value)) { for (const v of value) { - // Also use the helper for each item in the array - if (isEmptyValue(v)) { + if (v == null) { continue } - entries.push([`${key}[]`, String(v)]) + entries.push([key, String(v)]) } } else { entries.push([key, String(value)]) } } - if (!entries.length) { - return '' - } - const urlSearchParams = new URLSearchParams(entries) return urlSearchParams.toString() @@ -87,11 +79,43 @@ type QueryParams = queryParams?: ExtractQueryParams // Optional query params } -type GetRequestPathOptions = { +type IsNever = [Exclude] extends [never] ? true : false +export type NonNeverKeys = { + [Key in keyof Type]-?: IsNever extends true ? never : Key +}[keyof Type] +export type AllowedMethod = Extract, 'parameters'>, string> + +type JsonContentOf = Response extends { content: { 'application/json': infer T } } ? T : never + +type UnionJsonFromResponses = { + [StatusCode in keyof Response]: JsonContentOf +}[keyof Response] + +type StartingWithSuccessCode = { + [StatusCode in keyof Response]: `${StatusCode & number}` extends `2${number}${number}` ? StatusCode : never +}[keyof Response] + +type SuccessResponses = Pick, keyof Response>> + +type OperationOf> = paths[Path][Method] + +type ResponsesOf> = + OperationOf extends { responses: infer Response } ? Response : never + +type SuccessJson> = UnionJsonFromResponses< + SuccessResponses> +> + +export type SuccessJsonOrVoid> = [ + SuccessJson, +] extends [never] + ? void + : SuccessJson + +export type GetRequestPathOptions> = { path: Path method: Method - apiKey?: string - region: Region + region?: Region } & PathParams & QueryParams @@ -104,7 +128,6 @@ type GetRequestPathOptions} options * @param {Path} options.path - The path of the API endpoint * @param {string[]} [options.pathParams] - Path parameters to be replaced in the path - * @param {string} [options.apiKey] - API key to be included in the query string * @param {QueryParams["queryParams"]} [options.queryParams] - Query string * parameters to be appended to the URL * @param {Region} options.region - The region of the API endpoint @@ -113,10 +136,9 @@ type GetRequestPathOptions({ +export function getRequestPath>({ path, pathParams, - apiKey, queryParams, region, // method mention here so that it can be referenced in JSDoc @@ -127,7 +149,7 @@ export function getRequestPath match[1]) // Step 2: Replace the placeholders with provided pathParams - let formattedPath: string = path + let formattedPath: string = `${apiVersion}${path}` placeholders.forEach((placeholder, index) => { if (pathParams?.[index]) { formattedPath = formattedPath.replace(`{${placeholder}}`, pathParams[index]) @@ -140,11 +162,8 @@ export function getRequestPath # "eu" or "ap", "us" is the default REGION= +RULESET_ID= diff --git a/tests/functional-tests/CHANGELOG.md b/tests/functional-tests/CHANGELOG.md index b8bb39a4..f30033e2 100644 --- a/tests/functional-tests/CHANGELOG.md +++ b/tests/functional-tests/CHANGELOG.md @@ -1,4 +1,4 @@ -# fingerprintjs-pro-server-api-node-sdk-smoke-tests +# fingerprint-server-sdk-smoke-tests ## 1.0.6 diff --git a/tests/functional-tests/package.json b/tests/functional-tests/package.json index a51f7a9c..13b92cfc 100644 --- a/tests/functional-tests/package.json +++ b/tests/functional-tests/package.json @@ -1,6 +1,6 @@ { - "name": "fingerprintjs-pro-server-api-node-sdk-smoke-tests", - "version": "1.0.6", + "name": "fingerprint-server-sdk-smoke-tests", + "version": "2.0.0", "description": "", "main": "index.mjs", "private": true, @@ -10,7 +10,7 @@ "author": "", "license": "MIT", "dependencies": { - "@fingerprintjs/fingerprintjs-pro-server-api": "workspace:*", + "@fingerprint/fingerprint-server-sdk": "workspace:*", "dotenv": "^16.4.5" } } diff --git a/tests/functional-tests/smokeTests.mjs b/tests/functional-tests/smokeTests.mjs index adee4487..88c31981 100644 --- a/tests/functional-tests/smokeTests.mjs +++ b/tests/functional-tests/smokeTests.mjs @@ -1,16 +1,23 @@ -import { FingerprintJsServerApiClient, Region, RequestError, TooManyRequestsError } from '@fingerprintjs/fingerprintjs-pro-server-api' +import { + FingerprintServerApiClient, + Region, + RequestError, + TooManyRequestsError, +} from '@fingerprint/fingerprint-server-sdk' import { config } from 'dotenv' +import assert from "node:assert"; config() const REGION_MAP = { eu: Region.EU, ap: Region.AP, us: Region.Global } const region = REGION_MAP[(process.env.REGION ?? 'us').toLowerCase()] ?? Region.Global +const ruleset_id = process.env.RULESET_ID function createClient() { if (!process.env['API_KEY']) { throw new Error('Missing required API_KEY env variable!') } - return new FingerprintJsServerApiClient({ region, apiKey: process.env.API_KEY }) + return new FingerprintServerApiClient({ region, apiKey: process.env.API_KEY }) } async function getRecentEvents(client, start, end) { @@ -32,18 +39,79 @@ async function getRecentEvents(client, start, end) { return recent } -async function fetchEventAndVisitorDetails(client, firstEvent) { - const identification = firstEvent?.products?.identification?.data - const { visitorId, requestId } = identification - if (!requestId || !visitorId) { - throw new Error('Event missing requestId or visitorId') +async function fetchEventAndVisitorDetails(client, firstEvent, start, end) { + const { event_id: eventId, identification } = firstEvent + const visitorId = identification.visitor_id + if (!eventId || !visitorId) { + throw new Error('Event missing eventId or visitorId') } - console.log(`Retrieving event detail by requestId \`${requestId}\`...`) - await client.getEvent(requestId) + console.log(`Retrieving event detail by eventId \`${eventId}\`...`) + await client.getEvent(eventId) - console.log(`Retrieving visitor detail by visitorId \`${visitorId}\`...`) - await client.getVisits(visitorId, { limit: 10 }) + console.log(`Retrieving other events for visitorId \`${visitorId}\`...`) + const visitorEvents = await client.searchEvents({ visitor_id: visitorId, start, end, limit: 2 }) + if (visitorEvents.events.length === 0) { + throw new Error('Event missing for specific visitorId') + } +} + +async function validateRulesetEvaluationForBlock(client, start, end) { + console.log('Trying to find event with `incognito = true`...') + const search = await client.searchEvents({ limit: 1, incognito: true, start, end }) + + if (search.events.length === 0) { + console.warn('No event with `incognito = true`.') + return + } + + const event_id = search.events[0].event_id + const event = await client.getEvent(event_id, { ruleset_id }) + if (!event) { + throw new Error(`Event details are missing for found incognito event`) + } + + const expected = { + ruleset_id, + rule_id: 'r_PMwGXkWtG20KZn', + rule_expression: 'incognito', + type: 'block', + status_code: 403, + headers: [ { name: 'Content-Type', value: 'application/json' } ], + body: '{"message": "Incognito not allowed"}' + } + + assert.deepStrictEqual(event.rule_action, expected) + + console.log('Ruleset evaluation with `block` works!') +} + +async function validateRulesetEvaluationForAllow(client, start, end) { + console.log('Trying to find event with `incognito = false`...') + const search = await client.searchEvents({ limit: 1, incognito: false, start, end }) + + if (search.events.length === 0) { + console.warn('No event with `incognito = false`.') + return + } + + const event_id = search.events[0].event_id + const event = await client.getEvent(event_id, { ruleset_id }) + if (!event) { + throw new Error(`Event details are missing for found non-incognito event`) + } + + const expected = { + ruleset_id, + rule_id: 'r_OPnYaU9dKEke9X', + rule_expression: 'environment_id != "non-an-environment-id"', + type: 'allow', + request_header_modifications: { remove: [], set: [ { name: 'X-Allowed', value: 'true' } ], append: [] } + } + + assert.deepStrictEqual(event.rule_action, expected) + + console.log('Ruleset evaluation with `allow` works!') } async function validateOldestOrder(client, start, end) { @@ -54,10 +122,10 @@ async function validateOldestOrder(client, start, end) { } const [a, b] = old.events - const ts1 = a?.products?.identification?.data?.timestamp - const ts2 = b?.products?.identification?.data?.timestamp + const ts1 = a?.timestamp + const ts2 = b?.timestamp if (typeof ts1 !== 'number' || typeof ts2 !== 'number') { - throw new Error('Old events missing identification.data.timestamp') + throw new Error('Old events missing timestamp') } if (ts1 > ts2) { @@ -73,15 +141,20 @@ async function main() { const recent = await getRecentEvents(client, start, end) const [firstEvent] = recent.events - await fetchEventAndVisitorDetails(client, firstEvent) + await fetchEventAndVisitorDetails(client, firstEvent, start, end) await validateOldestOrder(client, start, end) + if (ruleset_id) { + await validateRulesetEvaluationForBlock(client, start, end) + await validateRulesetEvaluationForAllow(client, start, end) + } + console.log('All tests passed') return 0 } catch (error) { if (error instanceof TooManyRequestsError) { - console.error(`[TooManyRequestsError]: Rate limited. Retry after: ${error.retryAfter}s`) + console.error('[TooManyRequestsError]: Rate limited') return 1 } diff --git a/tests/mocked-responses-tests/castVisitorWebhookTest.spec.ts b/tests/mocked-responses-tests/castVisitorWebhookTest.spec.ts deleted file mode 100644 index 96dbfee7..00000000 --- a/tests/mocked-responses-tests/castVisitorWebhookTest.spec.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { Webhook } from '../../src/types' -import visitWebhookBody from './mocked-responses-data/webhook.json' - -describe('[Mocked body] Cast visitor webhook', () => { - test('with sample request body', async () => { - const visit = visitWebhookBody as Webhook - - // Assertion just to use the `visit` variable. The goal of this test is to assume that Typescript won't throw an error here. - expect(visit).toBeTruthy() - }) -}) diff --git a/tests/mocked-responses-tests/castWebhookEventTest.spec.ts b/tests/mocked-responses-tests/castWebhookEventTest.spec.ts new file mode 100644 index 00000000..0b1c2299 --- /dev/null +++ b/tests/mocked-responses-tests/castWebhookEventTest.spec.ts @@ -0,0 +1,11 @@ +import { Event } from '../../src' +import eventWebhookBody from './mocked-responses-data/webhook/webhook_event.json' + +describe('[Mocked body] Cast webhook event', () => { + test('with sample request body', async () => { + const event = eventWebhookBody as Event + + // Assertion just to use the `event` variable. The goal of this test is to assume that Typescript won't throw an error here. + expect(event).toBeTruthy() + }) +}) diff --git a/tests/mocked-responses-tests/deleteVisitorDataTests.spec.ts b/tests/mocked-responses-tests/deleteVisitorDataTests.spec.ts index b7d184e3..e112a7cc 100644 --- a/tests/mocked-responses-tests/deleteVisitorDataTests.spec.ts +++ b/tests/mocked-responses-tests/deleteVisitorDataTests.spec.ts @@ -1,16 +1,16 @@ import { ErrorResponse, - FingerprintJsServerApiClient, - getIntegrationInfo, + FingerprintServerApiClient, Region, RequestError, SdkError, TooManyRequestsError, } from '../../src' -import Error404 from './mocked-responses-data/errors/404_request_not_found.json' +import Error404 from './mocked-responses-data/errors/404_visitor_not_found.json' import Error403 from './mocked-responses-data/errors/403_feature_not_enabled.json' import Error400 from './mocked-responses-data/errors/400_visitor_id_invalid.json' import Error429 from './mocked-responses-data/errors/429_too_many_requests.json' +import { getIntegrationInfo } from '../../src/urlUtils' jest.spyOn(global, 'fetch') @@ -21,18 +21,18 @@ describe('[Mocked response] Delete visitor data', () => { const existingVisitorId = 'TaDnMBz9XCpZNuSzFUqP' - const client = new FingerprintJsServerApiClient({ region: Region.EU, apiKey }) + const client = new FingerprintServerApiClient({ region: Region.EU, apiKey }) test('with visitorId', async () => { - mockFetch.mockReturnValue(Promise.resolve(new Response())) + mockFetch.mockReturnValue(Promise.resolve(new Response(undefined, { headers: { 'content-length': '0' } }))) const response = await client.deleteVisitorData(existingVisitorId) expect(response).toBeUndefined() expect(mockFetch).toHaveBeenCalledWith( - `https://eu.api.fpjs.io/visitors/${existingVisitorId}?ii=${encodeURIComponent(getIntegrationInfo())}`, + `https://eu.api.fpjs.io/v4/visitors/${existingVisitorId}?ii=${encodeURIComponent(getIntegrationInfo())}`, { - headers: { 'Auth-API-Key': 'dummy_api_key' }, + headers: { Authorization: `Bearer ${apiKey}` }, method: 'DELETE', } ) @@ -41,6 +41,7 @@ describe('[Mocked response] Delete visitor data', () => { test('404 error', async () => { const mockResponse = new Response(JSON.stringify(Error404), { status: 404, + headers: { 'content-type': 'application/json' }, }) mockFetch.mockReturnValue(Promise.resolve(mockResponse)) @@ -52,6 +53,7 @@ describe('[Mocked response] Delete visitor data', () => { test('403 error', async () => { const mockResponse = new Response(JSON.stringify(Error403), { status: 403, + headers: { 'content-type': 'application/json' }, }) mockFetch.mockReturnValue(Promise.resolve(mockResponse)) @@ -63,6 +65,7 @@ describe('[Mocked response] Delete visitor data', () => { test('400 error', async () => { const mockResponse = new Response(JSON.stringify(Error400), { status: 400, + headers: { 'content-type': 'application/json' }, }) mockFetch.mockReturnValue(Promise.resolve(mockResponse)) @@ -74,15 +77,12 @@ describe('[Mocked response] Delete visitor data', () => { test('429 error', async () => { const mockResponse = new Response(JSON.stringify(Error429), { status: 429, - headers: { - 'retry-after': '5', - }, + headers: { 'content-type': 'application/json' }, }) mockFetch.mockReturnValue(Promise.resolve(mockResponse)) const expectedError = new TooManyRequestsError(Error429 as ErrorResponse, mockResponse) await expect(client.deleteVisitorData(existingVisitorId)).rejects.toThrow(expectedError) - expect(expectedError.retryAfter).toEqual(5) }) test('Error with bad JSON', async () => { @@ -108,6 +108,7 @@ describe('[Mocked response] Delete visitor data', () => { }), { status: 404, + headers: { 'content-type': 'application/json' }, } ) diff --git a/tests/mocked-responses-tests/getEventTests.spec.ts b/tests/mocked-responses-tests/getEventTests.spec.ts index c3fe2ef7..f777603e 100644 --- a/tests/mocked-responses-tests/getEventTests.spec.ts +++ b/tests/mocked-responses-tests/getEventTests.spec.ts @@ -1,62 +1,67 @@ -import { ErrorResponse, Region } from '../../src/types' -import { FingerprintJsServerApiClient } from '../../src/serverApiClient' -import getEventResponse from './mocked-responses-data/get_event_200.json' -import getEventWithExtraFieldsResponse from './mocked-responses-data/get_event_200_extra_fields.json' -import getEventAllErrorsResponse from './mocked-responses-data/get_event_200_all_errors.json' -import { RequestError, SdkError } from '../../src/errors/apiErrors' -import { getIntegrationInfo } from '../../src' +import { + ErrorResponse, + FingerprintServerApiClient, + Region, + RequestError, + SdkError, + TooManyRequestsError, +} from '../../src' +import getEventResponse from './mocked-responses-data/events/get_event_200.json' +import getEventRulesetResponse from './mocked-responses-data/events/get_event_ruleset_200.json' +import Error429 from './mocked-responses-data/errors/429_too_many_requests.json' +import { createJsonResponse } from './utils' +import { getIntegrationInfo } from '../../src/urlUtils' jest.spyOn(global, 'fetch') const mockFetch = fetch as unknown as jest.Mock describe('[Mocked response] Get Event', () => { const apiKey = 'dummy_api_key' - const existingRequestId = '1626550679751.cVc5Pm' + const existingEventId = '1626550679751.cVc5Pm' + const rulesetId = 'rs_b1k1blhqpOX3kU' - const client = new FingerprintJsServerApiClient({ region: Region.EU, apiKey }) + const client = new FingerprintServerApiClient({ region: Region.EU, apiKey }) - test('with request_id', async () => { - mockFetch.mockReturnValue(Promise.resolve(new Response(JSON.stringify(getEventResponse)))) + test('with event_id', async () => { + mockFetch.mockReturnValue(Promise.resolve(createJsonResponse(getEventResponse))) - const response = await client.getEvent(existingRequestId) + const response = await client.getEvent(existingEventId) expect(mockFetch).toHaveBeenCalledWith( - `https://eu.api.fpjs.io/events/${existingRequestId}?ii=${encodeURIComponent(getIntegrationInfo())}`, + `https://eu.api.fpjs.io/v4/events/${existingEventId}?ii=${encodeURIComponent(getIntegrationInfo())}`, { - headers: { 'Auth-API-Key': 'dummy_api_key' }, + headers: { Authorization: `Bearer ${apiKey}` }, method: 'GET', } ) expect(response).toEqual(getEventResponse) }) - test('with additional signals', async () => { - mockFetch.mockReturnValue(Promise.resolve(new Response(JSON.stringify(getEventWithExtraFieldsResponse)))) + test('with event_id and ruleset_id', async () => { + mockFetch.mockReturnValue(Promise.resolve(createJsonResponse(getEventRulesetResponse))) - const response = await client.getEvent(existingRequestId) - expect(response).toEqual(getEventWithExtraFieldsResponse) - }) - - test('with all signals with failed error', async () => { - mockFetch.mockReturnValue(Promise.resolve(new Response(JSON.stringify(getEventAllErrorsResponse)))) - - const response = await client.getEvent(existingRequestId) + const response = await client.getEvent(existingEventId, { ruleset_id: rulesetId }) - expect(response).toEqual(getEventAllErrorsResponse) + expect(mockFetch).toHaveBeenCalledWith( + `https://eu.api.fpjs.io/v4/events/${existingEventId}?ruleset_id=${rulesetId}&ii=${encodeURIComponent(getIntegrationInfo())}`, + { + headers: { Authorization: `Bearer ${apiKey}` }, + method: 'GET', + } + ) + expect(response).toEqual(getEventRulesetResponse) }) test('403 error', async () => { const errorInfo = { error: { - code: 'TokenRequired', + code: 'secret_api_key_required', message: 'secret key is required', }, } satisfies ErrorResponse - const mockResponse = new Response(JSON.stringify(errorInfo), { - status: 403, - }) + const mockResponse = createJsonResponse(errorInfo, 403) mockFetch.mockReturnValue(Promise.resolve(mockResponse)) - await expect(client.getEvent(existingRequestId)).rejects.toThrow( + await expect(client.getEvent(existingEventId)).rejects.toThrow( RequestError.fromErrorResponse(errorInfo, mockResponse) ) }) @@ -64,32 +69,40 @@ describe('[Mocked response] Get Event', () => { test('404 error', async () => { const errorInfo = { error: { - code: 'RequestNotFound', + code: 'request_not_found', message: 'request id is not found', }, } satisfies ErrorResponse - const mockResponse = new Response(JSON.stringify(errorInfo), { - status: 404, - }) + const mockResponse = createJsonResponse(errorInfo, 404) mockFetch.mockReturnValue(Promise.resolve(mockResponse)) - await expect(client.getEvent(existingRequestId)).rejects.toThrow( + await expect(client.getEvent(existingEventId)).rejects.toThrow( RequestError.fromErrorResponse(errorInfo, mockResponse) ) }) - test('Error with bad shape', async () => { - const errorInfo = 'Some text instead of shaped object' - const mockResponse = new Response( - JSON.stringify({ - error: errorInfo, - }), + test('Error with unknown', async () => { + const mockResponse = createJsonResponse( { - status: 404, - } + error: 'Unexpected error format', + }, + 404 ) mockFetch.mockReturnValue(Promise.resolve(mockResponse)) - await expect(client.getEvent(existingRequestId)).rejects.toThrow(RequestError) - await expect(client.getEvent(existingRequestId)).rejects.toThrow('Some text instead of shaped object') + await expect(client.getEvent(existingEventId)).rejects.toThrow(RequestError) + await expect(client.getEvent(existingEventId)).rejects.toThrow('Unknown error') + }) + + test('429 error with valid shape', async () => { + const mockResponse = createJsonResponse(Error429, 429) + mockFetch.mockReturnValue(Promise.resolve(mockResponse)) + await expect(client.getEvent(existingEventId)).rejects.toBeInstanceOf(TooManyRequestsError) + }) + + test('429 error with invalid shape', async () => { + const mockResponse = createJsonResponse({ reason: 'rate limited' }, 429) + mockFetch.mockReturnValue(Promise.resolve(mockResponse)) + await expect(client.getEvent(existingEventId)).rejects.toBeInstanceOf(RequestError) + await expect(client.getEvent(existingEventId)).rejects.not.toBeInstanceOf(TooManyRequestsError) }) test('Error with bad JSON', async () => { @@ -98,7 +111,7 @@ describe('[Mocked response] Get Event', () => { }) mockFetch.mockReturnValue(Promise.resolve(mockResponse)) - await expect(client.getEvent(existingRequestId)).rejects.toMatchObject( + await expect(client.getEvent(existingEventId)).rejects.toMatchObject( new SdkError( 'Failed to parse JSON response', mockResponse, diff --git a/tests/mocked-responses-tests/getRelatedVisitorsTests.spec.ts b/tests/mocked-responses-tests/getRelatedVisitorsTests.spec.ts deleted file mode 100644 index d543904a..00000000 --- a/tests/mocked-responses-tests/getRelatedVisitorsTests.spec.ts +++ /dev/null @@ -1,146 +0,0 @@ -import { ErrorResponse, Region } from '../../src/types' -import { FingerprintJsServerApiClient } from '../../src/serverApiClient' -import getRelatedVisitors from './mocked-responses-data/related-visitors/get_related_visitors_200.json' -import { RequestError, SdkError, TooManyRequestsError } from '../../src/errors/apiErrors' -import { getIntegrationInfo } from '../../src' - -jest.spyOn(global, 'fetch') - -const mockFetch = fetch as unknown as jest.Mock - -describe('[Mocked response] Get related Visitors', () => { - const apiKey = 'dummy_api_key' - const existingVisitorId = 'TaDnMBz9XCpZNuSzFUqP' - - const client = new FingerprintJsServerApiClient({ region: Region.EU, apiKey: apiKey }) - - test('without filter', async () => { - mockFetch.mockReturnValue(Promise.resolve(new Response(JSON.stringify(getRelatedVisitors)))) - - const response = await client.getRelatedVisitors({ - visitor_id: existingVisitorId, - }) - expect(response).toEqual(getRelatedVisitors) - expect(mockFetch).toHaveBeenCalledWith( - `https://eu.api.fpjs.io/related-visitors?visitor_id=${existingVisitorId}&ii=${encodeURIComponent(getIntegrationInfo())}`, - { - headers: { 'Auth-API-Key': 'dummy_api_key' }, - method: 'GET', - } - ) - }) - - test('400 error', async () => { - const error = { - error: { - message: 'Forbidden', - code: 'RequestCannotBeParsed', - }, - } satisfies ErrorResponse - const mockResponse = new Response(JSON.stringify(error), { - status: 400, - }) - mockFetch.mockReturnValue(Promise.resolve(mockResponse)) - await expect( - client.getRelatedVisitors({ - visitor_id: existingVisitorId, - }) - ).rejects.toThrow(RequestError.fromErrorResponse(error, mockResponse)) - }) - - test('403 error', async () => { - const error = { - error: { - message: 'secret key is required', - code: 'TokenRequired', - }, - } satisfies ErrorResponse - const mockResponse = new Response(JSON.stringify(error), { - status: 403, - }) - mockFetch.mockReturnValue(Promise.resolve(mockResponse)) - await expect( - client.getRelatedVisitors({ - visitor_id: existingVisitorId, - }) - ).rejects.toThrow(RequestError.fromErrorResponse(error, mockResponse)) - }) - - test('404 error', async () => { - const error = { - error: { - message: 'request id is not found', - code: 'RequestNotFound', - }, - } satisfies ErrorResponse - const mockResponse = new Response(JSON.stringify(error), { - status: 404, - }) - mockFetch.mockReturnValue(Promise.resolve(mockResponse)) - await expect( - client.getRelatedVisitors({ - visitor_id: existingVisitorId, - }) - ).rejects.toThrow(RequestError.fromErrorResponse(error, mockResponse)) - }) - - test('429 error', async () => { - const error = { - error: { - message: 'Too Many Requests', - code: 'TooManyRequests', - }, - } satisfies ErrorResponse - const mockResponse = new Response(JSON.stringify(error), { - status: 429, - headers: { 'Retry-after': '10' }, - }) - mockFetch.mockReturnValue(Promise.resolve(mockResponse)) - - const expectedError = new TooManyRequestsError(error, mockResponse) - await expect( - client.getRelatedVisitors({ - visitor_id: existingVisitorId, - }) - ).rejects.toThrow(expectedError) - expect(expectedError.retryAfter).toEqual(10) - }) - - test('429 error with empty retry-after header', async () => { - const error = { - error: { - message: 'Too Many Requests', - code: 'TooManyRequests', - }, - } satisfies ErrorResponse - const mockResponse = new Response(JSON.stringify(error), { - status: 429, - }) - mockFetch.mockReturnValue(Promise.resolve(mockResponse)) - const expectedError = new TooManyRequestsError(error, mockResponse) - await expect( - client.getRelatedVisitors({ - visitor_id: existingVisitorId, - }) - ).rejects.toThrow(expectedError) - expect(expectedError.retryAfter).toEqual(0) - }) - - test('Error with bad JSON', async () => { - const mockResponse = new Response('(Some bad JSON)', { - status: 404, - }) - mockFetch.mockReturnValue(Promise.resolve(mockResponse)) - await expect( - client.getRelatedVisitors({ - visitor_id: existingVisitorId, - }) - ).rejects.toMatchObject( - new SdkError( - 'Failed to parse JSON response', - mockResponse, - new SyntaxError('Unexpected token \'(\', "(Some bad JSON)" is not valid JSON') - ) - ) - }) -}) diff --git a/tests/mocked-responses-tests/getVisitorsTests.spec.ts b/tests/mocked-responses-tests/getVisitorsTests.spec.ts deleted file mode 100644 index 1f3eee29..00000000 --- a/tests/mocked-responses-tests/getVisitorsTests.spec.ts +++ /dev/null @@ -1,148 +0,0 @@ -import { ErrorPlainResponse, Region, VisitorHistoryFilter } from '../../src/types' -import { FingerprintJsServerApiClient } from '../../src/serverApiClient' -import getVisits from './mocked-responses-data/get_visitors_200_limit_1.json' -import { RequestError, SdkError, TooManyRequestsError } from '../../src/errors/apiErrors' -import { getIntegrationInfo } from '../../src' - -jest.spyOn(global, 'fetch') - -const mockFetch = fetch as unknown as jest.Mock - -describe('[Mocked response] Get Visitors', () => { - const apiKey = 'dummy_api_key' - const existingVisitorId = 'TaDnMBz9XCpZNuSzFUqP' - const existingRequestId = '1626550679751.cVc5Pm' - const existingLinkedId = 'makma' - - const client = new FingerprintJsServerApiClient({ region: Region.EU, apiKey: apiKey }) - - test('without filter', async () => { - mockFetch.mockReturnValue(Promise.resolve(new Response(JSON.stringify(getVisits)))) - - const response = await client.getVisits(existingVisitorId) - expect(response).toEqual(getVisits) - expect(mockFetch).toHaveBeenCalledWith( - `https://eu.api.fpjs.io/visitors/${existingVisitorId}?ii=${encodeURIComponent(getIntegrationInfo())}`, - { - headers: { 'Auth-API-Key': 'dummy_api_key' }, - method: 'GET', - } - ) - }) - - test('with request_id filter', async () => { - mockFetch.mockReturnValue(Promise.resolve(new Response(JSON.stringify(getVisits)))) - - const filter: VisitorHistoryFilter = { request_id: existingRequestId } - const response = await client.getVisits(existingVisitorId, filter) - expect(mockFetch).toHaveBeenCalledWith( - `https://eu.api.fpjs.io/visitors/${existingVisitorId}?request_id=${existingRequestId}&ii=${encodeURIComponent(getIntegrationInfo())}`, - { - headers: { 'Auth-API-Key': 'dummy_api_key' }, - method: 'GET', - } - ) - expect(response).toEqual(getVisits) - }) - - test('with request_id and linked_id filter', async () => { - mockFetch.mockReturnValue(Promise.resolve(new Response(JSON.stringify(getVisits)))) - - const filter: VisitorHistoryFilter = { - request_id: existingRequestId, - linked_id: existingLinkedId, - } - const response = await client.getVisits(existingVisitorId, filter) - expect(mockFetch).toHaveBeenCalledWith( - `https://eu.api.fpjs.io/visitors/${existingVisitorId}?request_id=${existingRequestId}&linked_id=${existingLinkedId}&ii=${encodeURIComponent(getIntegrationInfo())}`, - { - headers: { 'Auth-API-Key': 'dummy_api_key' }, - method: 'GET', - } - ) - expect(response).toEqual(getVisits) - }) - - test('with linked_id and limit filter', async () => { - mockFetch.mockReturnValue(Promise.resolve(new Response(JSON.stringify(getVisits)))) - - const filter: VisitorHistoryFilter = { linked_id: existingLinkedId, limit: 5 } - const response = await client.getVisits(existingVisitorId, filter) - expect(mockFetch).toHaveBeenCalledWith( - `https://eu.api.fpjs.io/visitors/${existingVisitorId}?linked_id=${existingLinkedId}&limit=5&ii=${encodeURIComponent(getIntegrationInfo())}`, - { - headers: { 'Auth-API-Key': 'dummy_api_key' }, - method: 'GET', - } - ) - expect(response).toEqual(getVisits) - }) - - test('with limit and before', async () => { - mockFetch.mockReturnValue(Promise.resolve(new Response(JSON.stringify(getVisits)))) - - const filter: VisitorHistoryFilter = { limit: 4, before: 1626538505244 } - const response = await client.getVisits(existingVisitorId, filter) - expect(mockFetch).toHaveBeenCalledWith( - `https://eu.api.fpjs.io/visitors/${existingVisitorId}?limit=4&before=1626538505244&ii=${encodeURIComponent(getIntegrationInfo())}`, - { - headers: { 'Auth-API-Key': 'dummy_api_key' }, - method: 'GET', - } - ) - expect(response).toEqual(getVisits) - }) - - test('403 error', async () => { - const error = { - error: 'Forbidden', - } satisfies ErrorPlainResponse - const mockResponse = new Response(JSON.stringify(error), { - status: 403, - }) - mockFetch.mockReturnValue(Promise.resolve(mockResponse)) - await expect(client.getVisits(existingVisitorId)).rejects.toThrow(RequestError.fromPlainError(error, mockResponse)) - }) - - test('429 error', async () => { - const error = { - error: 'Too Many Requests', - } - const mockResponse = new Response(JSON.stringify(error), { - status: 429, - headers: { 'Retry-after': '10' }, - }) - mockFetch.mockReturnValue(Promise.resolve(mockResponse)) - - const expectedError = TooManyRequestsError.fromPlain(error, mockResponse) - await expect(client.getVisits(existingVisitorId)).rejects.toThrow(expectedError) - expect(expectedError.retryAfter).toEqual(10) - }) - - test('429 error with empty retry-after header', async () => { - const error = { - error: 'Too Many Requests', - } satisfies ErrorPlainResponse - const mockResponse = new Response(JSON.stringify(error), { - status: 429, - }) - mockFetch.mockReturnValue(Promise.resolve(mockResponse)) - const expectedError = TooManyRequestsError.fromPlain(error, mockResponse) - await expect(client.getVisits(existingVisitorId)).rejects.toThrow(expectedError) - expect(expectedError.retryAfter).toEqual(0) - }) - - test('Error with bad JSON', async () => { - const mockResponse = new Response('(Some bad JSON)', { - status: 404, - }) - mockFetch.mockReturnValue(Promise.resolve(mockResponse)) - await expect(client.getVisits(existingVisitorId)).rejects.toMatchObject( - new SdkError( - 'Failed to parse JSON response', - mockResponse, - new SyntaxError('Unexpected token \'(\', "(Some bad JSON)" is not valid JSON') - ) - ) - }) -}) diff --git a/tests/mocked-responses-tests/mocked-responses-data/errors/400_bot_type_invalid.json b/tests/mocked-responses-tests/mocked-responses-data/errors/400_bot_type_invalid.json deleted file mode 100644 index 8dd65266..00000000 --- a/tests/mocked-responses-tests/mocked-responses-data/errors/400_bot_type_invalid.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "error": { - "code": "RequestCannotBeParsed", - "message": "invalid bot type" - } -} diff --git a/tests/mocked-responses-tests/mocked-responses-data/errors/400_end_time_invalid.json b/tests/mocked-responses-tests/mocked-responses-data/errors/400_end_time_invalid.json deleted file mode 100644 index 88654093..00000000 --- a/tests/mocked-responses-tests/mocked-responses-data/errors/400_end_time_invalid.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "error": { - "code": "RequestCannotBeParsed", - "message": "invalid end time" - } -} \ No newline at end of file diff --git a/tests/mocked-responses-tests/mocked-responses-data/errors/400_ip_address_invalid.json b/tests/mocked-responses-tests/mocked-responses-data/errors/400_ip_address_invalid.json deleted file mode 100644 index 5969bab6..00000000 --- a/tests/mocked-responses-tests/mocked-responses-data/errors/400_ip_address_invalid.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "error": { - "code": "RequestCannotBeParsed", - "message": "invalid ip address" - } -} \ No newline at end of file diff --git a/tests/mocked-responses-tests/mocked-responses-data/errors/400_limit_invalid.json b/tests/mocked-responses-tests/mocked-responses-data/errors/400_limit_invalid.json deleted file mode 100644 index 46297eb4..00000000 --- a/tests/mocked-responses-tests/mocked-responses-data/errors/400_limit_invalid.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "error": { - "code": "RequestCannotBeParsed", - "message": "invalid limit" - } -} diff --git a/tests/mocked-responses-tests/mocked-responses-data/errors/400_linked_id_invalid.json b/tests/mocked-responses-tests/mocked-responses-data/errors/400_linked_id_invalid.json deleted file mode 100644 index 72de54e0..00000000 --- a/tests/mocked-responses-tests/mocked-responses-data/errors/400_linked_id_invalid.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "error": { - "code": "RequestCannotBeParsed", - "message": "linked_id can't be greater than 256 characters long" - } -} diff --git a/tests/mocked-responses-tests/mocked-responses-data/errors/400_pagination_key_invalid.json b/tests/mocked-responses-tests/mocked-responses-data/errors/400_pagination_key_invalid.json deleted file mode 100644 index df559f9a..00000000 --- a/tests/mocked-responses-tests/mocked-responses-data/errors/400_pagination_key_invalid.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "error": { - "code": "RequestCannotBeParsed", - "message": "invalid pagination key" - } -} diff --git a/tests/mocked-responses-tests/mocked-responses-data/errors/400_request_body_invalid.json b/tests/mocked-responses-tests/mocked-responses-data/errors/400_request_body_invalid.json index ce56deff..c71fae96 100644 --- a/tests/mocked-responses-tests/mocked-responses-data/errors/400_request_body_invalid.json +++ b/tests/mocked-responses-tests/mocked-responses-data/errors/400_request_body_invalid.json @@ -1,6 +1,6 @@ { "error": { - "code": "RequestCannotBeParsed", + "code": "request_cannot_be_parsed", "message": "request body is not valid" } } diff --git a/tests/mocked-responses-tests/mocked-responses-data/errors/400_reverse_invalid.json b/tests/mocked-responses-tests/mocked-responses-data/errors/400_reverse_invalid.json deleted file mode 100644 index 540800fa..00000000 --- a/tests/mocked-responses-tests/mocked-responses-data/errors/400_reverse_invalid.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "error": { - "code": "RequestCannotBeParsed", - "message": "invalid reverse param" - } -} diff --git a/tests/mocked-responses-tests/mocked-responses-data/errors/400_start_time_invalid.json b/tests/mocked-responses-tests/mocked-responses-data/errors/400_start_time_invalid.json deleted file mode 100644 index 5d93f929..00000000 --- a/tests/mocked-responses-tests/mocked-responses-data/errors/400_start_time_invalid.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "error": { - "code": "RequestCannotBeParsed", - "message": "invalid start time" - } -} \ No newline at end of file diff --git a/tests/mocked-responses-tests/mocked-responses-data/errors/400_visitor_id_invalid.json b/tests/mocked-responses-tests/mocked-responses-data/errors/400_visitor_id_invalid.json index c204c568..ae7a3596 100644 --- a/tests/mocked-responses-tests/mocked-responses-data/errors/400_visitor_id_invalid.json +++ b/tests/mocked-responses-tests/mocked-responses-data/errors/400_visitor_id_invalid.json @@ -1,6 +1,6 @@ { "error": { - "code": "RequestCannotBeParsed", + "code": "request_cannot_be_parsed", "message": "invalid visitor id" } } diff --git a/tests/mocked-responses-tests/mocked-responses-data/errors/400_visitor_id_required.json b/tests/mocked-responses-tests/mocked-responses-data/errors/400_visitor_id_required.json deleted file mode 100644 index 6c5801a0..00000000 --- a/tests/mocked-responses-tests/mocked-responses-data/errors/400_visitor_id_required.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "error": { - "code": "RequestCannotBeParsed", - "message": "visitor id is required" - } -} diff --git a/tests/mocked-responses-tests/mocked-responses-data/errors/403_feature_not_enabled.json b/tests/mocked-responses-tests/mocked-responses-data/errors/403_feature_not_enabled.json index 9820a568..1478d51d 100644 --- a/tests/mocked-responses-tests/mocked-responses-data/errors/403_feature_not_enabled.json +++ b/tests/mocked-responses-tests/mocked-responses-data/errors/403_feature_not_enabled.json @@ -1,6 +1,6 @@ { "error": { - "code": "FeatureNotEnabled", + "code": "feature_not_enabled", "message": "feature not enabled" } } diff --git a/tests/mocked-responses-tests/mocked-responses-data/errors/403_subscription_not_active.json b/tests/mocked-responses-tests/mocked-responses-data/errors/403_subscription_not_active.json deleted file mode 100644 index 3deac898..00000000 --- a/tests/mocked-responses-tests/mocked-responses-data/errors/403_subscription_not_active.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "error": { - "code": "SubscriptionNotActive", - "message": "forbidden" - } -} diff --git a/tests/mocked-responses-tests/mocked-responses-data/errors/403_token_not_found.json b/tests/mocked-responses-tests/mocked-responses-data/errors/403_token_not_found.json deleted file mode 100644 index 3936b530..00000000 --- a/tests/mocked-responses-tests/mocked-responses-data/errors/403_token_not_found.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "error": { - "code": "TokenNotFound", - "message": "secret key is not found" - } -} diff --git a/tests/mocked-responses-tests/mocked-responses-data/errors/403_token_required.json b/tests/mocked-responses-tests/mocked-responses-data/errors/403_token_required.json deleted file mode 100644 index 544d8714..00000000 --- a/tests/mocked-responses-tests/mocked-responses-data/errors/403_token_required.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "error": { - "code": "TokenRequired", - "message": "secret key is required" - } -} diff --git a/tests/mocked-responses-tests/mocked-responses-data/errors/403_wrong_region.json b/tests/mocked-responses-tests/mocked-responses-data/errors/403_wrong_region.json deleted file mode 100644 index 8acc9e01..00000000 --- a/tests/mocked-responses-tests/mocked-responses-data/errors/403_wrong_region.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "error": { - "code": "WrongRegion", - "message": "wrong region" - } -} diff --git a/tests/mocked-responses-tests/mocked-responses-data/errors/404_event_not_found.json b/tests/mocked-responses-tests/mocked-responses-data/errors/404_event_not_found.json new file mode 100644 index 00000000..f7f7e542 --- /dev/null +++ b/tests/mocked-responses-tests/mocked-responses-data/errors/404_event_not_found.json @@ -0,0 +1,6 @@ +{ + "error": { + "code": "event_not_found", + "message": "event id not found" + } +} diff --git a/tests/mocked-responses-tests/mocked-responses-data/errors/404_request_not_found.json b/tests/mocked-responses-tests/mocked-responses-data/errors/404_request_not_found.json deleted file mode 100644 index 389b351c..00000000 --- a/tests/mocked-responses-tests/mocked-responses-data/errors/404_request_not_found.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "error": { - "code": "RequestNotFound", - "message": "request id is not found" - } -} diff --git a/tests/mocked-responses-tests/mocked-responses-data/errors/404_visitor_not_found.json b/tests/mocked-responses-tests/mocked-responses-data/errors/404_visitor_not_found.json index 11da4f3d..e4076f4f 100644 --- a/tests/mocked-responses-tests/mocked-responses-data/errors/404_visitor_not_found.json +++ b/tests/mocked-responses-tests/mocked-responses-data/errors/404_visitor_not_found.json @@ -1,6 +1,6 @@ { "error": { - "code": "VisitorNotFound", + "code": "visitor_not_found", "message": "visitor not found" } } diff --git a/tests/mocked-responses-tests/mocked-responses-data/errors/409_state_not_ready.json b/tests/mocked-responses-tests/mocked-responses-data/errors/409_state_not_ready.json index 36e6dde3..4ba3ac53 100644 --- a/tests/mocked-responses-tests/mocked-responses-data/errors/409_state_not_ready.json +++ b/tests/mocked-responses-tests/mocked-responses-data/errors/409_state_not_ready.json @@ -1,6 +1,6 @@ { "error": { - "code": "StateNotReady", + "code": "state_not_ready", "message": "resource is not mutable yet, try again" } } diff --git a/tests/mocked-responses-tests/mocked-responses-data/errors/429_too_many_requests.json b/tests/mocked-responses-tests/mocked-responses-data/errors/429_too_many_requests.json index e38639aa..bbbc7c41 100644 --- a/tests/mocked-responses-tests/mocked-responses-data/errors/429_too_many_requests.json +++ b/tests/mocked-responses-tests/mocked-responses-data/errors/429_too_many_requests.json @@ -1,6 +1,6 @@ { "error": { - "code": "TooManyRequests", + "code": "too_many_requests", "message": "too many requests" } } diff --git a/tests/mocked-responses-tests/mocked-responses-data/events/get_event_200.json b/tests/mocked-responses-tests/mocked-responses-data/events/get_event_200.json new file mode 100644 index 00000000..8985aa0e --- /dev/null +++ b/tests/mocked-responses-tests/mocked-responses-data/events/get_event_200.json @@ -0,0 +1,282 @@ +{ + "linked_id": "somelinkedId", + "tags": {}, + "timestamp": 1708102555327, + "event_id": "1708102555327.NLOjmg", + "url": "https://www.example.com/login?hope{this{works[!", + "ip_address": "61.127.217.15", + "user_agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64) ....", + "client_referrer": "https://example.com/blog/my-article", + "browser_details": { + "browser_name": "Chrome", + "browser_major_version": "74", + "browser_full_version": "74.0.3729", + "os": "Windows", + "os_version": "7", + "device": "Other" + }, + "identification": { + "visitor_id": "Ibk1527CUFmcnjLwIs4A9", + "confidence": { + "score": 0.97, + "version": "1.1" + }, + "visitor_found": false, + "first_seen_at": 1708102555327, + "last_seen_at": 1708102555327 + }, + "supplementary_id_high_recall": { + "visitor_id": "3HNey93AkBW6CRbxV6xP", + "visitor_found": true, + "confidence": { + "score": 0.97, + "version": "1.1" + }, + "first_seen_at": 1708102555327, + "last_seen_at": 1708102555327 + }, + "proximity": { + "id": "w1aTfd4MCvl", + "precision_radius": 10, + "confidence": 0.95 + }, + "bot": "not_detected", + "root_apps": false, + "emulator": false, + "ip_info": { + "v4": { + "address": "94.142.239.124", + "geolocation": { + "accuracy_radius": 20, + "latitude": 50.05, + "longitude": 14.4, + "postal_code": "150 00", + "timezone": "Europe/Prague", + "city_name": "Prague", + "country_code": "CZ", + "country_name": "Czechia", + "continent_code": "EU", + "continent_name": "Europe", + "subdivisions": [ + { + "iso_code": "10", + "name": "Hlavni mesto Praha" + } + ] + }, + "asn": "7922", + "asn_name": "COMCAST-7922", + "asn_network": "73.136.0.0/13", + "asn_type": "isp", + "datacenter_result": true, + "datacenter_name": "DediPath" + }, + "v6": { + "address": "2001:db8:3333:4444:5555:6666:7777:8888", + "geolocation": { + "accuracy_radius": 5, + "latitude": 49.982, + "longitude": 36.2566, + "postal_code": "10112", + "timezone": "Europe/Berlin", + "city_name": "Berlin", + "country_code": "DE", + "country_name": "Germany", + "continent_code": "EU", + "continent_name": "Europe", + "subdivisions": [ + { + "iso_code": "BE", + "name": "Land Berlin" + } + ] + }, + "asn": "6805", + "asn_name": "Telefonica Germany", + "asn_network": "2a02:3100::/24", + "asn_type": "isp", + "datacenter_result": false, + "datacenter_name": "" + } + }, + "ip_blocklist": { + "email_spam": false, + "attack_source": false, + "tor_node": false + }, + "proxy": true, + "proxy_confidence": "low", + "proxy_details": { + "proxy_type": "residential", + "last_seen_at": 1708102555327, + "provider": "Massive" + }, + "vpn": false, + "vpn_confidence": "high", + "vpn_origin_timezone": "Europe/Berlin", + "vpn_origin_country": "unknown", + "vpn_methods": { + "timezone_mismatch": false, + "public_vpn": false, + "auxiliary_mobile": false, + "os_mismatch": false, + "relay": false + }, + "incognito": false, + "tampering": false, + "tampering_details": { + "anomaly_score": 0.1955, + "anti_detect_browser": false + }, + "cloned_app": false, + "factory_reset_timestamp": 0, + "jailbroken": false, + "frida": false, + "privacy_settings": false, + "virtual_machine": false, + "location_spoofing": false, + "velocity": { + "distinct_ip": { + "5_minutes": 1, + "1_hour": 1, + "24_hours": 1 + }, + "distinct_country": { + "5_minutes": 1, + "1_hour": 2, + "24_hours": 2 + }, + "events": { + "5_minutes": 1, + "1_hour": 5, + "24_hours": 5 + }, + "ip_events": { + "5_minutes": 1, + "1_hour": 5, + "24_hours": 5 + }, + "distinct_ip_by_linked_id": { + "5_minutes": 1, + "1_hour": 5, + "24_hours": 5 + }, + "distinct_visitor_id_by_linked_id": { + "5_minutes": 1, + "1_hour": 5, + "24_hours": 5 + } + }, + "developer_tools": false, + "mitm_attack": false, + "sdk": { + "platform": "js", + "version": "3.11.10", + "integrations": [ + { + "name": "fingerprint-pro-react", + "version": "3.11.10", + "subintegration": { + "name": "preact", + "version": "10.21.0" + } + } + ] + }, + "replayed": false, + "high_activity_device": false, + "raw_device_attributes": { + "math": "5f030fa7d2e5f9f757bfaf81642eb1a6", + "vendor": "Google Inc.", + "plugins": [ + { + "description": "Portable Document Format", + "mimeTypes": [ + { + "suffixes": "pdf", + "type": "application/pdf" + }, + { + "suffixes": "pdf", + "type": "text/pdf" + } + ], + "name": "PDF Viewer" + } + ], + "webgl_extensions": { + "context_attributes": "6b1ed336830d2bc96442a9d76373252a", + "extension_parameters": "86a8abb36f0cb30b5946dec0c761d042", + "extensions": "57233d7b10f89fcd1ff95e3837ccd72d", + "parameters": "ea118c48e308bc4b0677118bbb3019ec", + "shader_precisions": "f223dfbcd580cf142da156d93790eb83", + "unsupported_extensions": [] + }, + "cookies_enabled": true, + "webgl_basics": { + "renderer": "WebKit WebGL", + "renderer_unmasked": "ANGLE (Apple, ANGLE Metal Renderer: Apple M4, Unspecified Version)", + "shading_language_version": "WebGL GLSL ES 1.0 (OpenGL ES GLSL ES 1.0 Chromium)", + "vendor": "WebKit", + "vendor_unmasked": "Google Inc. (Apple)", + "version": "WebGL 1.0 (OpenGL ES 2.0 Chromium)" + }, + "canvas": { + "geometry": "db3c1462576a399a03ae93d0ab9eb5c4", + "text": "70c3d3f7eb4408dc37a6bf8af1c51029", + "winding": true + }, + "hardware_concurrency": 10, + "languages": [ + [ + "en-US" + ] + ], + "color_depth": 24, + "fonts": [ + "Arial Unicode MS", + "Gill Sans", + "Helvetica Neue", + "Menlo" + ], + "indexed_db": true, + "touch_support": { + "max_touch_points": 0, + "touch_event": false, + "touch_start": false + }, + "device_memory": 8, + "oscpu": "Windows NT 6.1; Win64; x64", + "architecture": 127, + "screen_resolution": [ + 1920, + 1080 + ], + "timezone": "America/Sao_Paulo", + "emoji": { + "bottom": 32, + "font": "Times", + "height": 18, + "left": 8, + "right": 1608, + "top": 14, + "width": 1600, + "x": 8, + "y": 14 + }, + "font_preferences": { + "apple": 147.5625, + "default": 147.5625, + "min": 9.234375, + "mono": 133.0625, + "sans": 144.015625, + "serif": 147.5625, + "system": 146.09375 + }, + "platform": "MacIntel", + "local_storage": true, + "session_storage": true, + "date_time_locale": "en-US", + "audio": 124.04347745512496 + } +} \ No newline at end of file diff --git a/tests/mocked-responses-tests/mocked-responses-data/events/get_event_ruleset_200.json b/tests/mocked-responses-tests/mocked-responses-data/events/get_event_ruleset_200.json new file mode 100644 index 00000000..b02bfb2d --- /dev/null +++ b/tests/mocked-responses-tests/mocked-responses-data/events/get_event_ruleset_200.json @@ -0,0 +1,296 @@ +{ + "linked_id": "somelinkedId", + "tags": {}, + "timestamp": 1708102555327, + "event_id": "1708102555327.NLOjmg", + "url": "https://www.example.com/login?hope{this{works[!", + "ip_address": "61.127.217.15", + "user_agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64) ....", + "client_referrer": "https://example.com/blog/my-article", + "browser_details": { + "browser_name": "Chrome", + "browser_major_version": "74", + "browser_full_version": "74.0.3729", + "os": "Windows", + "os_version": "7", + "device": "Other" + }, + "identification": { + "visitor_id": "Ibk1527CUFmcnjLwIs4A9", + "confidence": { + "score": 0.97, + "version": "1.1" + }, + "visitor_found": false, + "first_seen_at": 1708102555327, + "last_seen_at": 1708102555327 + }, + "supplementary_id_high_recall": { + "visitor_id": "3HNey93AkBW6CRbxV6xP", + "visitor_found": true, + "confidence": { + "score": 0.97, + "version": "1.1" + }, + "first_seen_at": 1708102555327, + "last_seen_at": 1708102555327 + }, + "proximity": { + "id": "w1aTfd4MCvl", + "precision_radius": 10, + "confidence": 0.95 + }, + "bot": "not_detected", + "root_apps": false, + "emulator": false, + "ip_info": { + "v4": { + "address": "94.142.239.124", + "geolocation": { + "accuracy_radius": 20, + "latitude": 50.05, + "longitude": 14.4, + "postal_code": "150 00", + "timezone": "Europe/Prague", + "city_name": "Prague", + "country_code": "CZ", + "country_name": "Czechia", + "continent_code": "EU", + "continent_name": "Europe", + "subdivisions": [ + { + "iso_code": "10", + "name": "Hlavni mesto Praha" + } + ] + }, + "asn": "7922", + "asn_name": "COMCAST-7922", + "asn_network": "73.136.0.0/13", + "asn_type": "isp", + "datacenter_result": true, + "datacenter_name": "DediPath" + }, + "v6": { + "address": "2001:db8:3333:4444:5555:6666:7777:8888", + "geolocation": { + "accuracy_radius": 5, + "latitude": 49.982, + "longitude": 36.2566, + "postal_code": "10112", + "timezone": "Europe/Berlin", + "city_name": "Berlin", + "country_code": "DE", + "country_name": "Germany", + "continent_code": "EU", + "continent_name": "Europe", + "subdivisions": [ + { + "iso_code": "BE", + "name": "Land Berlin" + } + ] + }, + "asn": "6805", + "asn_name": "Telefonica Germany", + "asn_network": "2a02:3100::/24", + "asn_type": "isp", + "datacenter_result": false, + "datacenter_name": "" + } + }, + "ip_blocklist": { + "email_spam": false, + "attack_source": false, + "tor_node": false + }, + "proxy": true, + "proxy_confidence": "low", + "proxy_details": { + "proxy_type": "residential", + "last_seen_at": 1708102555327, + "provider": "Massive" + }, + "vpn": false, + "vpn_confidence": "high", + "vpn_origin_timezone": "Europe/Berlin", + "vpn_origin_country": "unknown", + "vpn_methods": { + "timezone_mismatch": false, + "public_vpn": false, + "auxiliary_mobile": false, + "os_mismatch": false, + "relay": false + }, + "incognito": false, + "tampering": false, + "tampering_details": { + "anomaly_score": 0.1955, + "anti_detect_browser": false + }, + "cloned_app": false, + "factory_reset_timestamp": 0, + "jailbroken": false, + "frida": false, + "privacy_settings": false, + "virtual_machine": false, + "location_spoofing": false, + "velocity": { + "distinct_ip": { + "5_minutes": 1, + "1_hour": 1, + "24_hours": 1 + }, + "distinct_country": { + "5_minutes": 1, + "1_hour": 2, + "24_hours": 2 + }, + "events": { + "5_minutes": 1, + "1_hour": 5, + "24_hours": 5 + }, + "ip_events": { + "5_minutes": 1, + "1_hour": 5, + "24_hours": 5 + }, + "distinct_ip_by_linked_id": { + "5_minutes": 1, + "1_hour": 5, + "24_hours": 5 + }, + "distinct_visitor_id_by_linked_id": { + "5_minutes": 1, + "1_hour": 5, + "24_hours": 5 + } + }, + "developer_tools": false, + "mitm_attack": false, + "sdk": { + "platform": "js", + "version": "3.11.10", + "integrations": [ + { + "name": "fingerprint-pro-react", + "version": "3.11.10", + "subintegration": { + "name": "preact", + "version": "10.21.0" + } + } + ] + }, + "replayed": false, + "high_activity_device": false, + "raw_device_attributes": { + "math": "5f030fa7d2e5f9f757bfaf81642eb1a6", + "vendor": "Google Inc.", + "plugins": [ + { + "description": "Portable Document Format", + "mimeTypes": [ + { + "suffixes": "pdf", + "type": "application/pdf" + }, + { + "suffixes": "pdf", + "type": "text/pdf" + } + ], + "name": "PDF Viewer" + } + ], + "webgl_extensions": { + "context_attributes": "6b1ed336830d2bc96442a9d76373252a", + "extension_parameters": "86a8abb36f0cb30b5946dec0c761d042", + "extensions": "57233d7b10f89fcd1ff95e3837ccd72d", + "parameters": "ea118c48e308bc4b0677118bbb3019ec", + "shader_precisions": "f223dfbcd580cf142da156d93790eb83", + "unsupported_extensions": [] + }, + "cookies_enabled": true, + "webgl_basics": { + "renderer": "WebKit WebGL", + "renderer_unmasked": "ANGLE (Apple, ANGLE Metal Renderer: Apple M4, Unspecified Version)", + "shading_language_version": "WebGL GLSL ES 1.0 (OpenGL ES GLSL ES 1.0 Chromium)", + "vendor": "WebKit", + "vendor_unmasked": "Google Inc. (Apple)", + "version": "WebGL 1.0 (OpenGL ES 2.0 Chromium)" + }, + "canvas": { + "geometry": "db3c1462576a399a03ae93d0ab9eb5c4", + "text": "70c3d3f7eb4408dc37a6bf8af1c51029", + "winding": true + }, + "hardware_concurrency": 10, + "languages": [ + [ + "en-US" + ] + ], + "color_depth": 24, + "fonts": [ + "Arial Unicode MS", + "Gill Sans", + "Helvetica Neue", + "Menlo" + ], + "indexed_db": true, + "touch_support": { + "max_touch_points": 0, + "touch_event": false, + "touch_start": false + }, + "device_memory": 8, + "oscpu": "Windows NT 6.1; Win64; x64", + "architecture": 127, + "screen_resolution": [ + 1920, + 1080 + ], + "timezone": "America/Sao_Paulo", + "emoji": { + "bottom": 32, + "font": "Times", + "height": 18, + "left": 8, + "right": 1608, + "top": 14, + "width": 1600, + "x": 8, + "y": 14 + }, + "font_preferences": { + "apple": 147.5625, + "default": 147.5625, + "min": 9.234375, + "mono": 133.0625, + "sans": 144.015625, + "serif": 147.5625, + "system": 146.09375 + }, + "platform": "MacIntel", + "local_storage": true, + "session_storage": true, + "date_time_locale": "en-US", + "audio": 124.04347745512496 + }, + "rule_action": { + "type": "block", + "status_code": 403, + "headers": [ + { + "name": "Content-Type", + "value": "application/json" + } + ], + "body": "{\"title\":\"Forbidden\"}", + "ruleset_id": "rs_b1k1blhqpOX3kU", + "rule_id": "r_uE0af8497PFAOD", + "rule_expression": "bot in [\"bad\"] || incognito" + } +} \ No newline at end of file diff --git a/tests/mocked-responses-tests/mocked-responses-data/events/search/get_event_search_200.json b/tests/mocked-responses-tests/mocked-responses-data/events/search/get_event_search_200.json new file mode 100644 index 00000000..2affda77 --- /dev/null +++ b/tests/mocked-responses-tests/mocked-responses-data/events/search/get_event_search_200.json @@ -0,0 +1,287 @@ +{ + "events": [ + { + "linked_id": "somelinkedId", + "tags": {}, + "timestamp": 1708102555327, + "event_id": "1708102555327.NLOjmg", + "url": "https://www.example.com/login?hope{this{works[!", + "ip_address": "61.127.217.15", + "user_agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64) ....", + "client_referrer": "https://example.com/blog/my-article", + "browser_details": { + "browser_name": "Chrome", + "browser_major_version": "74", + "browser_full_version": "74.0.3729", + "os": "Windows", + "os_version": "7", + "device": "Other" + }, + "identification": { + "visitor_id": "Ibk1527CUFmcnjLwIs4A9", + "confidence": { + "score": 0.97, + "version": "1.1" + }, + "visitor_found": false, + "first_seen_at": 1708102555327, + "last_seen_at": 1708102555327 + }, + "supplementary_id_high_recall": { + "visitor_id": "3HNey93AkBW6CRbxV6xP", + "visitor_found": true, + "confidence": { + "score": 0.97, + "version": "1.1" + }, + "first_seen_at": 1708102555327, + "last_seen_at": 1708102555327 + }, + "proximity": { + "id": "w1aTfd4MCvl", + "precision_radius": 10, + "confidence": 0.95 + }, + "bot": "not_detected", + "root_apps": false, + "emulator": false, + "ip_info": { + "v4": { + "address": "94.142.239.124", + "geolocation": { + "accuracy_radius": 20, + "latitude": 50.05, + "longitude": 14.4, + "postal_code": "150 00", + "timezone": "Europe/Prague", + "city_name": "Prague", + "country_code": "CZ", + "country_name": "Czechia", + "continent_code": "EU", + "continent_name": "Europe", + "subdivisions": [ + { + "iso_code": "10", + "name": "Hlavni mesto Praha" + } + ] + }, + "asn": "7922", + "asn_name": "COMCAST-7922", + "asn_network": "73.136.0.0/13", + "asn_type": "isp", + "datacenter_result": true, + "datacenter_name": "DediPath" + }, + "v6": { + "address": "2001:db8:3333:4444:5555:6666:7777:8888", + "geolocation": { + "accuracy_radius": 5, + "latitude": 49.982, + "longitude": 36.2566, + "postal_code": "10112", + "timezone": "Europe/Berlin", + "city_name": "Berlin", + "country_code": "DE", + "country_name": "Germany", + "continent_code": "EU", + "continent_name": "Europe", + "subdivisions": [ + { + "iso_code": "BE", + "name": "Land Berlin" + } + ] + }, + "asn": "6805", + "asn_name": "Telefonica Germany", + "asn_network": "2a02:3100::/24", + "asn_type": "isp", + "datacenter_result": false, + "datacenter_name": "" + } + }, + "ip_blocklist": { + "email_spam": false, + "attack_source": false, + "tor_node": false + }, + "proxy": true, + "proxy_confidence": "low", + "proxy_details": { + "proxy_type": "residential", + "last_seen_at": 1708102555327, + "provider": "Massive" + }, + "vpn": false, + "vpn_confidence": "high", + "vpn_origin_timezone": "Europe/Berlin", + "vpn_origin_country": "unknown", + "vpn_methods": { + "timezone_mismatch": false, + "public_vpn": false, + "auxiliary_mobile": false, + "os_mismatch": false, + "relay": false + }, + "incognito": false, + "tampering": false, + "tampering_details": { + "anomaly_score": 0.1955, + "anti_detect_browser": false + }, + "cloned_app": false, + "factory_reset_timestamp": 0, + "jailbroken": false, + "frida": false, + "privacy_settings": false, + "virtual_machine": false, + "location_spoofing": false, + "velocity": { + "distinct_ip": { + "5_minutes": 1, + "1_hour": 1, + "24_hours": 1 + }, + "distinct_country": { + "5_minutes": 1, + "1_hour": 2, + "24_hours": 2 + }, + "events": { + "5_minutes": 1, + "1_hour": 5, + "24_hours": 5 + }, + "ip_events": { + "5_minutes": 1, + "1_hour": 5, + "24_hours": 5 + }, + "distinct_ip_by_linked_id": { + "5_minutes": 1, + "1_hour": 5, + "24_hours": 5 + }, + "distinct_visitor_id_by_linked_id": { + "5_minutes": 1, + "1_hour": 5, + "24_hours": 5 + } + }, + "developer_tools": false, + "mitm_attack": false, + "sdk": { + "platform": "js", + "version": "3.11.10", + "integrations": [ + { + "name": "fingerprint-pro-react", + "version": "3.11.10", + "subintegration": { + "name": "preact", + "version": "10.21.0" + } + } + ] + }, + "replayed": false, + "high_activity_device": false, + "raw_device_attributes": { + "math": "5f030fa7d2e5f9f757bfaf81642eb1a6", + "vendor": "Google Inc.", + "plugins": [ + { + "description": "Portable Document Format", + "mimeTypes": [ + { + "suffixes": "pdf", + "type": "application/pdf" + }, + { + "suffixes": "pdf", + "type": "text/pdf" + } + ], + "name": "PDF Viewer" + } + ], + "webgl_extensions": { + "context_attributes": "6b1ed336830d2bc96442a9d76373252a", + "extension_parameters": "86a8abb36f0cb30b5946dec0c761d042", + "extensions": "57233d7b10f89fcd1ff95e3837ccd72d", + "parameters": "ea118c48e308bc4b0677118bbb3019ec", + "shader_precisions": "f223dfbcd580cf142da156d93790eb83", + "unsupported_extensions": [] + }, + "cookies_enabled": true, + "webgl_basics": { + "renderer": "WebKit WebGL", + "renderer_unmasked": "ANGLE (Apple, ANGLE Metal Renderer: Apple M4, Unspecified Version)", + "shading_language_version": "WebGL GLSL ES 1.0 (OpenGL ES GLSL ES 1.0 Chromium)", + "vendor": "WebKit", + "vendor_unmasked": "Google Inc. (Apple)", + "version": "WebGL 1.0 (OpenGL ES 2.0 Chromium)" + }, + "canvas": { + "geometry": "db3c1462576a399a03ae93d0ab9eb5c4", + "text": "70c3d3f7eb4408dc37a6bf8af1c51029", + "winding": true + }, + "hardware_concurrency": 10, + "languages": [ + [ + "en-US" + ] + ], + "color_depth": 24, + "fonts": [ + "Arial Unicode MS", + "Gill Sans", + "Helvetica Neue", + "Menlo" + ], + "indexed_db": true, + "touch_support": { + "max_touch_points": 0, + "touch_event": false, + "touch_start": false + }, + "device_memory": 8, + "oscpu": "Windows NT 6.1; Win64; x64", + "architecture": 127, + "screen_resolution": [ + 1920, + 1080 + ], + "timezone": "America/Sao_Paulo", + "emoji": { + "bottom": 32, + "font": "Times", + "height": 18, + "left": 8, + "right": 1608, + "top": 14, + "width": 1600, + "x": 8, + "y": 14 + }, + "font_preferences": { + "apple": 147.5625, + "default": 147.5625, + "min": 9.234375, + "mono": 133.0625, + "sans": 144.015625, + "serif": 147.5625, + "system": 146.09375 + }, + "platform": "MacIntel", + "local_storage": true, + "session_storage": true, + "date_time_locale": "en-US", + "audio": 124.04347745512496 + } + } + ], + "pagination_key": "1708102555327" +} \ No newline at end of file diff --git a/tests/mocked-responses-tests/mocked-responses-data/get_event_200.json b/tests/mocked-responses-tests/mocked-responses-data/get_event_200.json deleted file mode 100644 index 7560b9a6..00000000 --- a/tests/mocked-responses-tests/mocked-responses-data/get_event_200.json +++ /dev/null @@ -1,354 +0,0 @@ -{ - "products": { - "identification": { - "data": { - "visitorId": "Ibk1527CUFmcnjLwIs4A9", - "requestId": "1708102555327.NLOjmg", - "incognito": true, - "linkedId": "somelinkedId", - "tag": {}, - "time": "2019-05-21T16:40:13Z", - "timestamp": 1582299576512, - "url": "https://www.example.com/login?hope{this{works[!", - "ip": "61.127.217.15", - "ipLocation": { - "accuracyRadius": 10, - "latitude": 49.982, - "longitude": 36.2566, - "postalCode": "61202", - "timezone": "Europe/Dusseldorf", - "city": { - "name": "Dusseldorf" - }, - "country": { - "code": "DE", - "name": "Germany" - }, - "continent": { - "code": "EU", - "name": "Europe" - }, - "subdivisions": [ - { - "isoCode": "63", - "name": "North Rhine-Westphalia" - } - ] - }, - "browserDetails": { - "browserName": "Chrome", - "browserMajorVersion": "74", - "browserFullVersion": "74.0.3729", - "os": "Windows", - "osVersion": "7", - "device": "Other", - "userAgent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64) ...." - }, - "confidence": { - "score": 0.97 - }, - "visitorFound": false, - "firstSeenAt": { - "global": "2022-03-16T11:26:45.362Z", - "subscription": "2022-03-16T11:31:01.101Z" - }, - "lastSeenAt": { - "global": null, - "subscription": null - }, - "sdk": { - "platform": "js", - "version": "3.11.10" - }, - "replayed": false - } - }, - "botd": { - "data": { - "bot": { - "result": "notDetected" - }, - "url": "https://www.example.com/login?hope{this{works}[!", - "ip": "61.127.217.15", - "time": "2019-05-21T16:40:13Z", - "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 YaBrowser/24.1.0.0 Safari/537.36", - "requestId": "1708102555327.NLOjmg" - } - }, - "rootApps": { - "data": { - "result": false - } - }, - "emulator": { - "data": { - "result": false - } - }, - "ipInfo": { - "data": { - "v4": { - "address": "94.142.239.124", - "geolocation": { - "accuracyRadius": 20, - "latitude": 50.05, - "longitude": 14.4, - "postalCode": "150 00", - "timezone": "Europe/Prague", - "city": { - "name": "Prague" - }, - "country": { - "code": "CZ", - "name": "Czechia" - }, - "continent": { - "code": "EU", - "name": "Europe" - }, - "subdivisions": [ - { - "isoCode": "10", - "name": "Hlavni mesto Praha" - } - ] - }, - "asn": { - "asn": "7922", - "name": "COMCAST-7922", - "network": "73.136.0.0/13" - }, - "datacenter": { - "result": true, - "name": "DediPath" - } - }, - "v6": { - "address": "2001:db8:3333:4444:5555:6666:7777:8888", - "geolocation": { - "accuracyRadius": 5, - "latitude": 49.982, - "longitude": 36.2566, - "postalCode": "10112", - "timezone": "Europe/Berlin", - "city": { - "name": "Berlin" - }, - "country": { - "code": "DE", - "name": "Germany" - }, - "continent": { - "code": "EU", - "name": "Europe" - }, - "subdivisions": [ - { - "isoCode": "BE", - "name": "Land Berlin" - } - ] - }, - "asn": { - "asn": "6805", - "name": "Telefonica Germany", - "network": "2a02:3100::/24" - }, - "datacenter": { - "result": false, - "name": "" - } - } - } - }, - "ipBlocklist": { - "data": { - "result": false, - "details": { - "emailSpam": false, - "attackSource": false - } - } - }, - "tor": { - "data": { - "result": false - } - }, - "vpn": { - "data": { - "result": false, - "confidence": "high", - "originTimezone": "Europe/Berlin", - "originCountry": "unknown", - "methods": { - "timezoneMismatch": false, - "publicVPN": false, - "auxiliaryMobile": false, - "osMismatch": false, - "relay": false - } - } - }, - "proxy": { - "data": { - "result": true, - "confidence": "high", - "details": { - "proxyType": "residential", - "lastSeenAt": "2025-08-12T13:00:00Z" - } - } - }, - "incognito": { - "data": { - "result": false - } - }, - "tampering": { - "data": { - "result": false, - "anomalyScore": 0.1955, - "antiDetectBrowser": false - } - }, - "clonedApp": { - "data": { - "result": false - } - }, - "factoryReset": { - "data": { - "time": "1970-01-01T00:00:00Z", - "timestamp": 0 - } - }, - "jailbroken": { - "data": { - "result": false - } - }, - "frida": { - "data": { - "result": false - } - }, - "privacySettings": { - "data": { - "result": false - } - }, - "virtualMachine": { - "data": { - "result": false - } - }, - "rawDeviceAttributes": { - "data": { - "architecture": { - "value": 127 - }, - "audio": { - "value": 35.73832903057337 - }, - "canvas": { - "value": { - "Winding": true, - "Geometry": "4dce9d6017c3e0c052a77252f29f2b1c", - "Text": "dd2474a56ff78c1de3e7a07070ba3b7d" - } - }, - "colorDepth": { - "value": 30 - }, - "colorGamut": { - "value": "p3" - }, - "contrast": { - "value": 0 - }, - "cookiesEnabled": { - "value": true - }, - "cpuClass": {}, - "fonts": { - "value": ["Arial Unicode MS", "Gill Sans", "Helvetica Neue", "Menlo"] - } - } - }, - "highActivity": { - "data": { - "result": false - } - }, - "locationSpoofing": { - "data": { - "result": false - } - }, - "velocity": { - "data": { - "distinctIp": { - "intervals": { - "5m": 1, - "1h": 1, - "24h": 1 - } - }, - "distinctLinkedId": {}, - "distinctCountry": { - "intervals": { - "5m": 1, - "1h": 2, - "24h": 2 - } - }, - "events": { - "intervals": { - "5m": 1, - "1h": 5, - "24h": 5 - } - }, - "ipEvents": { - "intervals": { - "5m": 1, - "1h": 5, - "24h": 5 - } - }, - "distinctIpByLinkedId": { - "intervals": { - "5m": 1, - "1h": 5, - "24h": 5 - } - }, - "distinctVisitorIdByLinkedId": { - "intervals": { - "5m": 1, - "1h": 5, - "24h": 5 - } - } - } - }, - "developerTools": { - "data": { - "result": false - } - }, - "mitmAttack": { - "data": { - "result": false - } - }, - "proximity": { - "data": { - "id": "w1aTfd4MCvl", - "precisionRadius": 10, - "confidence": 0.95 - } - } - } -} diff --git a/tests/mocked-responses-tests/mocked-responses-data/get_event_200_all_errors.json b/tests/mocked-responses-tests/mocked-responses-data/get_event_200_all_errors.json deleted file mode 100644 index 15ea2037..00000000 --- a/tests/mocked-responses-tests/mocked-responses-data/get_event_200_all_errors.json +++ /dev/null @@ -1,164 +0,0 @@ -{ - "products": { - "identification": { - "error": { - "code": "Failed", - "message": "internal server error" - } - }, - "botd": { - "error": { - "code": "Failed", - "message": "internal server error" - } - }, - "ipInfo": { - "error": { - "code": "Failed", - "message": "internal server error" - } - }, - "incognito": { - "error": { - "code": "Failed", - "message": "internal server error" - } - }, - "rootApps": { - "error": { - "code": "Failed", - "message": "internal server error" - } - }, - "clonedApp": { - "error": { - "code": "Failed", - "message": "internal server error" - } - }, - "factoryReset": { - "error": { - "code": "Failed", - "message": "internal server error" - } - }, - "jailbroken": { - "error": { - "code": "Failed", - "message": "internal server error" - } - }, - "frida": { - "error": { - "code": "Failed", - "message": "internal server error" - } - }, - "emulator": { - "error": { - "code": "Failed", - "message": "internal server error" - } - }, - "ipBlocklist": { - "error": { - "code": "Failed", - "message": "internal server error" - } - }, - "tor": { - "error": { - "code": "Failed", - "message": "internal server error" - } - }, - "vpn": { - "error": { - "code": "Failed", - "message": "internal server error" - } - }, - "proxy": { - "error": { - "code": "Failed", - "message": "internal server error" - } - }, - "privacySettings": { - "error": { - "code": "Failed", - "message": "internal server error" - } - }, - "virtualMachine": { - "error": { - "code": "Failed", - "message": "internal server error" - } - }, - "tampering": { - "error": { - "code": "Failed", - "message": "internal server error" - } - }, - "rawDeviceAttributes": { - "data": { - "audio": { - "error": { - "name": "Error", - "message": "internal server error" - } - }, - "canvas": { - "error": { - "name": "Error", - "message": "internal server error" - } - } - } - }, - "locationSpoofing": { - "error": { - "code": "Failed", - "message": "internal server error" - } - }, - "highActivity": { - "error": { - "code": "Failed", - "message": "internal server error" - } - }, - "suspectScore": { - "error": { - "code": "Failed", - "message": "internal server error" - } - }, - "velocity": { - "error": { - "code": "Failed", - "message": "internal server error" - } - }, - "developerTools": { - "error": { - "code": "Failed", - "message": "internal server error" - } - }, - "mitmAttack": { - "error": { - "code": "Failed", - "message": "internal server error" - } - }, - "proximity": { - "error": { - "code": "Failed", - "message": "internal server error" - } - } - } -} diff --git a/tests/mocked-responses-tests/mocked-responses-data/get_event_200_botd_failed_error.json b/tests/mocked-responses-tests/mocked-responses-data/get_event_200_botd_failed_error.json deleted file mode 100644 index 0afa5b79..00000000 --- a/tests/mocked-responses-tests/mocked-responses-data/get_event_200_botd_failed_error.json +++ /dev/null @@ -1,69 +0,0 @@ -{ - "products": { - "identification": { - "data": { - "visitorId": "Ibk1527CUFmcnjLwIs4A9", - "requestId": "0KSh65EnVoB85JBmloQK", - "incognito": true, - "linkedId": "somelinkedId", - "time": "2019-05-21T16:40:13Z", - "tag": {}, - "timestamp": 1582299576512, - "url": "https://www.example.com/login", - "ip": "61.127.217.15", - "ipLocation": { - "accuracyRadius": 10, - "latitude": 49.982, - "longitude": 36.2566, - "postalCode": "61202", - "timezone": "Europe/Dusseldorf", - "city": { - "name": "Dusseldorf" - }, - "continent": { - "code": "EU", - "name": "Europe" - }, - "country": { - "code": "DE", - "name": "Germany" - }, - "subdivisions": [ - { - "isoCode": "63", - "name": "North Rhine-Westphalia" - } - ] - }, - "browserDetails": { - "browserName": "Chrome", - "browserMajorVersion": "74", - "browserFullVersion": "74.0.3729", - "os": "Windows", - "osVersion": "7", - "device": "Other", - "userAgent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64) ...." - }, - "confidence": { - "score": 0.97 - }, - "visitorFound": true, - "firstSeenAt": { - "global": "2022-03-16T11:26:45.362Z", - "subscription": "2022-03-16T11:31:01.101Z" - }, - "lastSeenAt": { - "global": "2022-03-16T11:28:34.023Z", - "subscription": null - }, - "replayed": false - } - }, - "botd": { - "error": { - "code": "Failed", - "message": "internal server error" - } - } - } -} diff --git a/tests/mocked-responses-tests/mocked-responses-data/get_event_200_extra_fields.json b/tests/mocked-responses-tests/mocked-responses-data/get_event_200_extra_fields.json deleted file mode 100644 index 5a56a1c7..00000000 --- a/tests/mocked-responses-tests/mocked-responses-data/get_event_200_extra_fields.json +++ /dev/null @@ -1,91 +0,0 @@ -{ - "products": { - "identification": { - "data": { - "visitorId": "Ibk1527CUFmcnjLwIs4A9", - "requestId": "0KSh65EnVoB85JBmloQK", - "incognito": true, - "linkedId": "somelinkedId", - "tag": {}, - "time": "2019-05-21T16:40:13Z", - "timestamp": 1582299576512, - "url": "https://www.example.com/login", - "ip": "61.127.217.15", - "ipLocation": { - "accuracyRadius": 10, - "latitude": 49.982, - "longitude": 36.2566, - "postalCode": "61202", - "timezone": "Europe/Dusseldorf", - "city": { - "name": "Dusseldorf" - }, - "continent": { - "code": "EU", - "name": "Europe" - }, - "country": { - "code": "DE", - "name": "Germany" - }, - "subdivisions": [ - { - "isoCode": "63", - "name": "North Rhine-Westphalia" - } - ] - }, - "browserDetails": { - "browserName": "Chrome", - "browserMajorVersion": "74", - "browserFullVersion": "74.0.3729", - "os": "Windows", - "osVersion": "7", - "device": "Other", - "userAgent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64) ...." - }, - "confidence": { - "score": 0.97, - "revision": "v1.1" - }, - "visitorFound": true, - "firstSeenAt": { - "global": "2022-03-16T11:26:45.362Z", - "subscription": "2022-03-16T11:31:01.101Z" - }, - "lastSeenAt": { - "global": "2022-03-16T11:28:34.023Z", - "subscription": null - }, - "replayed": false - } - }, - "botd": { - "data": { - "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 YaBrowser/24.1.0.0 Safari/537.36", - "requestId": "1708102555327.NLOjmg", - "bot": { - "result": "notDetected" - }, - "url": "https://www.example.com/login", - "ip": "61.127.217.15", - "time": "2019-05-21T16:40:13Z" - } - }, - "product3": { - "data": { - "result": false - } - }, - "product4": { - "data": { - "result": true, - "details": { - "detail1": true, - "detail2": "detail description", - "detail3": 42 - } - } - } - } -} diff --git a/tests/mocked-responses-tests/mocked-responses-data/get_event_200_identification_failed_error.json b/tests/mocked-responses-tests/mocked-responses-data/get_event_200_identification_failed_error.json deleted file mode 100644 index 4739f36e..00000000 --- a/tests/mocked-responses-tests/mocked-responses-data/get_event_200_identification_failed_error.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "products": { - "identification": { - "error": { - "code": "Failed", - "message": "internal server error" - } - }, - "botd": { - "data": { - "bot": { - "result": "bad", - "type": "headlessChrome" - }, - "url": "https://example.com/login", - "ip": "94.60.143.223", - "time": "2024-02-23T10:20:25.287Z", - "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/121.0.6167.57 Safari/537.36", - "requestId": "1708683625245.tuJ4nD" - } - } - } -} diff --git a/tests/mocked-responses-tests/mocked-responses-data/get_event_200_too_many_requests_error.json b/tests/mocked-responses-tests/mocked-responses-data/get_event_200_too_many_requests_error.json deleted file mode 100644 index 138aae72..00000000 --- a/tests/mocked-responses-tests/mocked-responses-data/get_event_200_too_many_requests_error.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "products": { - "identification": { - "error": { - "code": "429 Too Many Requests", - "message": "too many requests" - } - }, - "botd": { - "error": { - "code": "TooManyRequests", - "message": "too many requests" - } - } - } -} diff --git a/tests/mocked-responses-tests/mocked-responses-data/get_event_200_with_broken_format.json b/tests/mocked-responses-tests/mocked-responses-data/get_event_200_with_broken_format.json deleted file mode 100644 index 58081140..00000000 --- a/tests/mocked-responses-tests/mocked-responses-data/get_event_200_with_broken_format.json +++ /dev/null @@ -1,301 +0,0 @@ -{ - "products": { - "identification": { - "data": { - "visitorId": "Ibk1527CUFmcnjLwIs4A9", - "requestId": "1708102555327.NLOjmg", - "incognito": true, - "linkedId": { - "broken": "format" - }, - "tag": {}, - "time": "2019-05-21T16:40:13Z", - "timestamp": 1582299576512, - "url": "https://www.example.com/login?hope{this{works[!", - "ip": "61.127.217.15", - "ipLocation": { - "accuracyRadius": 10, - "latitude": 49.982, - "longitude": 36.2566, - "postalCode": "61202", - "timezone": "Europe/Dusseldorf", - "city": { - "name": "Dusseldorf" - }, - "country": { - "code": "DE", - "name": "Germany" - }, - "continent": { - "code": "EU", - "name": "Europe" - }, - "subdivisions": [ - { - "isoCode": "63", - "name": "North Rhine-Westphalia" - } - ] - }, - "browserDetails": { - "browserName": "Chrome", - "browserMajorVersion": "74", - "browserFullVersion": "74.0.3729", - "os": "Windows", - "osVersion": "7", - "device": "Other", - "userAgent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64) ...." - }, - "confidence": { - "score": 0.97 - }, - "visitorFound": false, - "firstSeenAt": { - "global": "2022-03-16T11:26:45.362Z", - "subscription": "2022-03-16T11:31:01.101Z" - }, - "lastSeenAt": { - "global": null, - "subscription": null - }, - "replayed": false - } - }, - "botd": { - "data": { - "bot": { - "result": "notDetected" - }, - "url": "https://www.example.com/login?hope{this{works}[!", - "ip": "61.127.217.15", - "time": "2019-05-21T16:40:13Z", - "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 YaBrowser/24.1.0.0 Safari/537.36", - "requestId": "1708102555327.NLOjmg" - } - }, - "rootApps": { - "data": { - "result": false - } - }, - "emulator": { - "data": { - "result": false - } - }, - "ipInfo": { - "data": { - "v4": { - "address": "94.142.239.124", - "geolocation": { - "accuracyRadius": 20, - "latitude": 50.05, - "longitude": 14.4, - "postalCode": "150 00", - "timezone": "Europe/Prague", - "city": { - "name": "Prague" - }, - "country": { - "code": "CZ", - "name": "Czechia" - }, - "continent": { - "code": "EU", - "name": "Europe" - }, - "subdivisions": [ - { - "isoCode": "10", - "name": "Hlavni mesto Praha" - } - ] - }, - "asn": { - "asn": "7922", - "name": "COMCAST-7922", - "network": "73.136.0.0/13" - }, - "datacenter": { - "result": true, - "name": "DediPath" - } - }, - "v6": { - "address": "2001:db8:3333:4444:5555:6666:7777:8888", - "geolocation": { - "accuracyRadius": 5, - "latitude": 49.982, - "longitude": 36.2566, - "postalCode": "10112", - "timezone": "Europe/Berlin", - "city": { - "name": "Berlin" - }, - "country": { - "code": "DE", - "name": "Germany" - }, - "continent": { - "code": "EU", - "name": "Europe" - }, - "subdivisions": [ - { - "isoCode": "BE", - "name": "Land Berlin" - } - ] - }, - "asn": { - "asn": "6805", - "name": "Telefonica Germany", - "network": "2a02:3100::/24" - }, - "datacenter": { - "result": false, - "name": "" - } - } - } - }, - "ipBlocklist": { - "data": { - "result": false, - "details": { - "emailSpam": false, - "attackSource": false - } - } - }, - "tor": { - "data": { - "result": false - } - }, - "vpn": { - "data": { - "result": false, - "originTimezone": "Europe/Berlin", - "originCountry": "unknown", - "methods": { - "timezoneMismatch": false, - "publicVPN": false, - "auxiliaryMobile": false - } - } - }, - "proxy": { - "data": { - "result": true, - "confidence": "high", - "details": { - "proxyType": "residential", - "lastSeenAt": "2025-08-12T13:00:00Z" - } - } - }, - "incognito": { - "data": { - "result": false - } - }, - "tampering": { - "data": { - "result": false, - "anomalyScore": 0.1955 - } - }, - "clonedApp": { - "data": { - "result": false - } - }, - "factoryReset": { - "data": { - "time": "1970-01-01T00:00:00Z", - "timestamp": 0 - } - }, - "jailbroken": { - "data": { - "result": false - } - }, - "frida": { - "data": { - "result": false - } - }, - "privacySettings": { - "data": { - "result": false - } - }, - "virtualMachine": { - "data": { - "result": false - } - }, - "rawDeviceAttributes": { - "data": { - "architecture": { - "value": 127 - }, - "audio": { - "value": 35.73832903057337 - }, - "canvas": { - "value": { - "Winding": true, - "Geometry": "4dce9d6017c3e0c052a77252f29f2b1c", - "Text": "dd2474a56ff78c1de3e7a07070ba3b7d" - } - }, - "colorDepth": { - "value": 30 - }, - "colorGamut": { - "value": "p3" - }, - "contrast": { - "value": 0 - }, - "cookiesEnabled": { - "value": true - }, - "cpuClass": {}, - "fonts": { - "value": [ - "Arial Unicode MS", - "Gill Sans", - "Helvetica Neue", - "Menlo" - ] - } - } - }, - "highActivity": { - "data": { - "result": false - } - }, - "locationSpoofing": { - "data": { - "result": false - } - }, - "mitmAttack": { - "data": { - "result": false - } - }, - "proximity": { - "data": { - "id": "w1aTfd4MCvl", - "precisionRadius": 10, - "confidence": 0.95 - } - } - } -} diff --git a/tests/mocked-responses-tests/mocked-responses-data/get_event_200_with_unknown_field.json b/tests/mocked-responses-tests/mocked-responses-data/get_event_200_with_unknown_field.json deleted file mode 100644 index 6af6ad63..00000000 --- a/tests/mocked-responses-tests/mocked-responses-data/get_event_200_with_unknown_field.json +++ /dev/null @@ -1,299 +0,0 @@ -{ - "unknown": "field", - "products": { - "unknown": "field", - "identification": { - "unknown": "field", - "data": { - "unknown": "field", - "visitorId": "Ibk1527CUFmcnjLwIs4A9", - "requestId": "1708102555327.NLOjmg", - "incognito": true, - "linkedId": "somelinkedId", - "tag": {}, - "time": "2019-05-21T16:40:13Z", - "timestamp": 1582299576512, - "url": "https://www.example.com/login?hope{this{works[!", - "ip": "61.127.217.15", - "ipLocation": { - "accuracyRadius": 10, - "latitude": 49.982, - "longitude": 36.2566, - "postalCode": "61202", - "timezone": "Europe/Dusseldorf", - "city": { - "name": "Dusseldorf" - }, - "country": { - "code": "DE", - "name": "Germany" - }, - "continent": { - "code": "EU", - "name": "Europe" - }, - "subdivisions": [ - { - "isoCode": "63", - "name": "North Rhine-Westphalia" - } - ] - }, - "browserDetails": { - "browserName": "Chrome", - "browserMajorVersion": "74", - "browserFullVersion": "74.0.3729", - "os": "Windows", - "osVersion": "7", - "device": "Other", - "userAgent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64) ...." - }, - "confidence": { - "score": 0.97 - }, - "visitorFound": false, - "firstSeenAt": { - "global": "2022-03-16T11:26:45.362Z", - "subscription": "2022-03-16T11:31:01.101Z" - }, - "lastSeenAt": { - "global": null, - "subscription": null - }, - "replayed": false - } - }, - "botd": { - "data": { - "bot": { - "result": "notDetected" - }, - "url": "https://www.example.com/login?hope{this{works}[!", - "ip": "61.127.217.15", - "time": "2019-05-21T16:40:13Z", - "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 YaBrowser/24.1.0.0 Safari/537.36", - "requestId": "1708102555327.NLOjmg" - } - }, - "rootApps": { - "data": { - "result": false - } - }, - "emulator": { - "data": { - "result": false - } - }, - "ipInfo": { - "data": { - "v4": { - "address": "94.142.239.124", - "geolocation": { - "accuracyRadius": 20, - "latitude": 50.05, - "longitude": 14.4, - "postalCode": "150 00", - "timezone": "Europe/Prague", - "city": { - "name": "Prague" - }, - "country": { - "code": "CZ", - "name": "Czechia" - }, - "continent": { - "code": "EU", - "name": "Europe" - }, - "subdivisions": [ - { - "isoCode": "10", - "name": "Hlavni mesto Praha" - } - ] - }, - "asn": { - "asn": "7922", - "name": "COMCAST-7922", - "network": "73.136.0.0/13" - }, - "datacenter": { - "result": true, - "name": "DediPath" - } - }, - "v6": { - "address": "2001:db8:3333:4444:5555:6666:7777:8888", - "geolocation": { - "accuracyRadius": 5, - "latitude": 49.982, - "longitude": 36.2566, - "postalCode": "10112", - "timezone": "Europe/Berlin", - "city": { - "name": "Berlin" - }, - "country": { - "code": "DE", - "name": "Germany" - }, - "continent": { - "code": "EU", - "name": "Europe" - }, - "subdivisions": [ - { - "isoCode": "BE", - "name": "Land Berlin" - } - ] - }, - "asn": { - "asn": "6805", - "name": "Telefonica Germany", - "network": "2a02:3100::/24" - }, - "datacenter": { - "result": false, - "name": "" - } - } - } - }, - "ipBlocklist": { - "data": { - "result": false, - "details": { - "emailSpam": false, - "attackSource": false - } - } - }, - "tor": { - "data": { - "result": false - } - }, - "vpn": { - "data": { - "result": false, - "originTimezone": "Europe/Berlin", - "originCountry": "unknown", - "methods": { - "timezoneMismatch": false, - "publicVPN": false, - "auxiliaryMobile": false - } - } - }, - "proxy": { - "data": { - "result": false, - "confidence": "high" - } - }, - "incognito": { - "data": { - "result": false - } - }, - "tampering": { - "data": { - "result": false, - "anomalyScore": 0.1955 - } - }, - "clonedApp": { - "data": { - "result": false - } - }, - "factoryReset": { - "data": { - "time": "1970-01-01T00:00:00Z", - "timestamp": 0 - } - }, - "jailbroken": { - "data": { - "result": false - } - }, - "frida": { - "data": { - "result": false - } - }, - "privacySettings": { - "data": { - "result": false - } - }, - "virtualMachine": { - "data": { - "result": false - } - }, - "rawDeviceAttributes": { - "data": { - "architecture": { - "value": 127 - }, - "audio": { - "value": 35.73832903057337 - }, - "canvas": { - "value": { - "Winding": true, - "Geometry": "4dce9d6017c3e0c052a77252f29f2b1c", - "Text": "dd2474a56ff78c1de3e7a07070ba3b7d" - } - }, - "colorDepth": { - "value": 30 - }, - "colorGamut": { - "value": "p3" - }, - "contrast": { - "value": 0 - }, - "cookiesEnabled": { - "value": true - }, - "cpuClass": {}, - "fonts": { - "value": [ - "Arial Unicode MS", - "Gill Sans", - "Helvetica Neue", - "Menlo" - ] - } - } - }, - "highActivity": { - "data": { - "result": false - } - }, - "locationSpoofing": { - "data": { - "result": false - } - }, - "mitmAttack": { - "data": { - "result": false - } - }, - "proximity": { - "data": { - "id": "w1aTfd4MCvl", - "precisionRadius": 10, - "confidence": 0.95 - } - } - } -} diff --git a/tests/mocked-responses-tests/mocked-responses-data/get_event_search_200.json b/tests/mocked-responses-tests/mocked-responses-data/get_event_search_200.json deleted file mode 100644 index 27b7848d..00000000 --- a/tests/mocked-responses-tests/mocked-responses-data/get_event_search_200.json +++ /dev/null @@ -1,354 +0,0 @@ -{ - "events": [ - { - "products": { - "identification": { - "data": { - "visitorId": "Ibk1527CUFmcnjLwIs4A9", - "requestId": "1708102555327.NLOjmg", - "incognito": true, - "linkedId": "somelinkedId", - "tag": {}, - "time": "2019-05-21T16:40:13Z", - "timestamp": 1582299576512, - "url": "https://www.example.com/login?hope{this{works[!", - "ip": "61.127.217.15", - "ipLocation": { - "accuracyRadius": 10, - "latitude": 49.982, - "longitude": 36.2566, - "postalCode": "61202", - "timezone": "Europe/Dusseldorf", - "city": { - "name": "Dusseldorf" - }, - "country": { - "code": "DE", - "name": "Germany" - }, - "continent": { - "code": "EU", - "name": "Europe" - }, - "subdivisions": [ - { - "isoCode": "63", - "name": "North Rhine-Westphalia" - } - ] - }, - "browserDetails": { - "browserName": "Chrome", - "browserMajorVersion": "74", - "browserFullVersion": "74.0.3729", - "os": "Windows", - "osVersion": "7", - "device": "Other", - "userAgent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64) ...." - }, - "confidence": { - "score": 0.97 - }, - "visitorFound": false, - "firstSeenAt": { - "global": "2022-03-16T11:26:45.362Z", - "subscription": "2022-03-16T11:31:01.101Z" - }, - "lastSeenAt": { - "global": null, - "subscription": null - }, - "replayed": false - } - }, - "botd": { - "data": { - "bot": { - "result": "notDetected" - }, - "url": "https://www.example.com/login?hope{this{works}[!", - "ip": "61.127.217.15", - "time": "2019-05-21T16:40:13Z", - "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 YaBrowser/24.1.0.0 Safari/537.36", - "requestId": "1708102555327.NLOjmg" - } - }, - "rootApps": { - "data": { - "result": false - } - }, - "emulator": { - "data": { - "result": false - } - }, - "ipInfo": { - "data": { - "v4": { - "address": "94.142.239.124", - "geolocation": { - "accuracyRadius": 20, - "latitude": 50.05, - "longitude": 14.4, - "postalCode": "150 00", - "timezone": "Europe/Prague", - "city": { - "name": "Prague" - }, - "country": { - "code": "CZ", - "name": "Czechia" - }, - "continent": { - "code": "EU", - "name": "Europe" - }, - "subdivisions": [ - { - "isoCode": "10", - "name": "Hlavni mesto Praha" - } - ] - }, - "asn": { - "asn": "7922", - "name": "COMCAST-7922", - "network": "73.136.0.0/13" - }, - "datacenter": { - "result": true, - "name": "DediPath" - } - }, - "v6": { - "address": "2001:db8:3333:4444:5555:6666:7777:8888", - "geolocation": { - "accuracyRadius": 5, - "latitude": 49.982, - "longitude": 36.2566, - "postalCode": "10112", - "timezone": "Europe/Berlin", - "city": { - "name": "Berlin" - }, - "country": { - "code": "DE", - "name": "Germany" - }, - "continent": { - "code": "EU", - "name": "Europe" - }, - "subdivisions": [ - { - "isoCode": "BE", - "name": "Land Berlin" - } - ] - }, - "asn": { - "asn": "6805", - "name": "Telefonica Germany", - "network": "2a02:3100::/24" - }, - "datacenter": { - "result": false, - "name": "" - } - } - } - }, - "ipBlocklist": { - "data": { - "result": false, - "details": { - "emailSpam": false, - "attackSource": false - } - } - }, - "tor": { - "data": { - "result": false - } - }, - "vpn": { - "data": { - "result": false, - "confidence": "high", - "originTimezone": "Europe/Berlin", - "originCountry": "unknown", - "methods": { - "timezoneMismatch": false, - "publicVPN": false, - "auxiliaryMobile": false, - "osMismatch": false, - "relay": false - } - } - }, - "proxy": { - "data": { - "result": false, - "confidence": "high", - "details": { - "proxyType": "residential", - "lastSeenAt": "2025-08-12T13:00:00Z" - } - } - }, - "incognito": { - "data": { - "result": false - } - }, - "tampering": { - "data": { - "result": false, - "anomalyScore": 0.1955, - "antiDetectBrowser": false - } - }, - "clonedApp": { - "data": { - "result": false - } - }, - "factoryReset": { - "data": { - "time": "1970-01-01T00:00:00Z", - "timestamp": 0 - } - }, - "jailbroken": { - "data": { - "result": false - } - }, - "frida": { - "data": { - "result": false - } - }, - "privacySettings": { - "data": { - "result": false - } - }, - "virtualMachine": { - "data": { - "result": false - } - }, - "rawDeviceAttributes": { - "data": { - "architecture": { - "value": 127 - }, - "audio": { - "value": 35.73832903057337 - }, - "canvas": { - "value": { - "Winding": true, - "Geometry": "4dce9d6017c3e0c052a77252f29f2b1c", - "Text": "dd2474a56ff78c1de3e7a07070ba3b7d" - } - }, - "colorDepth": { - "value": 30 - }, - "colorGamut": { - "value": "p3" - }, - "contrast": { - "value": 0 - }, - "cookiesEnabled": { - "value": true - }, - "cpuClass": {}, - "fonts": { - "value": ["Arial Unicode MS", "Gill Sans", "Helvetica Neue", "Menlo"] - } - } - }, - "highActivity": { - "data": { - "result": false - } - }, - "locationSpoofing": { - "data": { - "result": false - } - }, - "velocity": { - "data": { - "distinctIp": { - "intervals": { - "5m": 1, - "1h": 1, - "24h": 1 - } - }, - "distinctLinkedId": {}, - "distinctCountry": { - "intervals": { - "5m": 1, - "1h": 2, - "24h": 2 - } - }, - "events": { - "intervals": { - "5m": 1, - "1h": 5, - "24h": 5 - } - }, - "ipEvents": { - "intervals": { - "5m": 1, - "1h": 5, - "24h": 5 - } - }, - "distinctIpByLinkedId": { - "intervals": { - "5m": 1, - "1h": 5, - "24h": 5 - } - }, - "distinctVisitorIdByLinkedId": { - "intervals": { - "5m": 1, - "1h": 5, - "24h": 5 - } - } - } - }, - "developerTools": { - "data": { - "result": false - } - }, - "mitmAttack": { - "data": { - "result": false - } - }, - "proximity": { - "data": { - "id": "w1aTfd4MCvl", - "precisionRadius": 10, - "confidence": 0.95 - } - } - }} - ], - "paginationKey": "1655373953086" -} diff --git a/tests/mocked-responses-tests/mocked-responses-data/get_visitors_200_limit_1.json b/tests/mocked-responses-tests/mocked-responses-data/get_visitors_200_limit_1.json deleted file mode 100644 index f6357ea5..00000000 --- a/tests/mocked-responses-tests/mocked-responses-data/get_visitors_200_limit_1.json +++ /dev/null @@ -1,61 +0,0 @@ -{ - "visitorId": "AcxioeQKffpXF8iGQK3P", - "visits": [ - { - "requestId": "1655373953086.DDlfmP", - "browserDetails": { - "browserName": "Chrome", - "browserMajorVersion": "102", - "browserFullVersion": "102.0.5005", - "os": "Mac OS X", - "osVersion": "10.15.7", - "device": "Other", - "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.61 Safari/537.36" - }, - "incognito": false, - "ip": "82.118.30.68", - "ipLocation": { - "accuracyRadius": 1000, - "latitude": 50.0805, - "longitude": 14.467, - "postalCode": "130 00", - "timezone": "Europe/Prague", - "city": { - "name": "Prague" - }, - "country": { - "code": "CZ", - "name": "Czechia" - }, - "continent": { - "code": "EU", - "name": "Europe" - }, - "subdivisions": [ - { - "isoCode": "10", - "name": "Hlavni mesto Praha" - } - ] - }, - "timestamp": 1655373953094, - "time": "2022-06-16T10:05:53Z", - "url": "https://dashboard.fingerprint.com/", - "tag": {}, - "confidence": { - "score": 1 - }, - "visitorFound": true, - "firstSeenAt": { - "global": "2022-02-04T11:31:20Z", - "subscription": "2022-02-04T11:31:20Z" - }, - "lastSeenAt": { - "global": "2022-06-16T10:03:00.912Z", - "subscription": "2022-06-16T10:03:00.912Z" - } - } - ], - "lastTimestamp": 1655373953086, - "paginationKey": "1655373953086.DDlfmP" -} diff --git a/tests/mocked-responses-tests/mocked-responses-data/get_visitors_200_limit_500.json b/tests/mocked-responses-tests/mocked-responses-data/get_visitors_200_limit_500.json deleted file mode 100644 index 3e3aceb0..00000000 --- a/tests/mocked-responses-tests/mocked-responses-data/get_visitors_200_limit_500.json +++ /dev/null @@ -1,3030 +0,0 @@ -{ - "visitorId": "AcxioeQKffpXF8iGQK3P", - "visits": [ - { - "requestId": "1655373780901.HhjRFX", - "browserDetails": { - "browserName": "Chrome", - "browserMajorVersion": "102", - "browserFullVersion": "102.0.5005", - "os": "Mac OS X", - "osVersion": "10.15.7", - "device": "Other", - "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.61 Safari/537.36" - }, - "incognito": false, - "ip": "188.242.36.107", - "ipLocation": { - "accuracyRadius": 5, - "latitude": 59.8983, - "longitude": 30.2618, - "postalCode": "190924", - "timezone": "Europe/Moscow", - "city": { - "name": "St Petersburg" - }, - "country": { - "code": "RU", - "name": "Russia" - }, - "continent": { - "code": "EU", - "name": "Europe" - }, - "subdivisions": [ - { - "isoCode": "SPE", - "name": "St.-Petersburg" - } - ] - }, - "timestamp": 1655373780912, - "time": "2022-06-16T10:03:00Z", - "url": "https://fingerprint.com/", - "tag": {}, - "confidence": { - "score": 1 - }, - "visitorFound": true, - "firstSeenAt": { - "global": "2022-02-04T11:31:20Z", - "subscription": "2022-02-04T11:31:20Z" - }, - "lastSeenAt": { - "global": "2022-06-16T05:27:30.578Z", - "subscription": "2022-06-16T05:27:30.578Z" - } - }, - { - "requestId": "1655357250568.vqejDF", - "browserDetails": { - "browserName": "Chrome", - "browserMajorVersion": "102", - "browserFullVersion": "102.0.5005", - "os": "Mac OS X", - "osVersion": "10.15.7", - "device": "Other", - "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.61 Safari/537.36" - }, - "incognito": false, - "ip": "82.118.30.62", - "ipLocation": { - "accuracyRadius": 1000, - "latitude": 50.0805, - "longitude": 14.467, - "postalCode": "130 00", - "timezone": "Europe/Prague", - "city": { - "name": "Prague" - }, - "country": { - "code": "CZ", - "name": "Czechia" - }, - "continent": { - "code": "EU", - "name": "Europe" - }, - "subdivisions": [ - { - "isoCode": "10", - "name": "Hlavni mesto Praha" - } - ] - }, - "timestamp": 1655357250578, - "time": "2022-06-16T05:27:30Z", - "url": "https://fingerprint.com/", - "tag": {}, - "confidence": { - "score": 0.99 - }, - "visitorFound": true, - "firstSeenAt": { - "global": "2022-02-04T11:31:20Z", - "subscription": "2022-02-04T11:31:20Z" - }, - "lastSeenAt": { - "global": "2022-06-15T15:28:33.479Z", - "subscription": "2022-06-15T15:28:33.479Z" - } - }, - { - "requestId": "1655306913474.kFQsQx", - "browserDetails": { - "browserName": "Chrome", - "browserMajorVersion": "102", - "browserFullVersion": "102.0.5005", - "os": "Mac OS X", - "osVersion": "10.15.7", - "device": "Other", - "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.61 Safari/537.36" - }, - "incognito": false, - "ip": "82.118.30.68", - "ipLocation": { - "accuracyRadius": 1000, - "latitude": 50.0805, - "longitude": 14.467, - "postalCode": "130 00", - "timezone": "Europe/Prague", - "city": { - "name": "Prague" - }, - "country": { - "code": "CZ", - "name": "Czechia" - }, - "continent": { - "code": "EU", - "name": "Europe" - }, - "subdivisions": [ - { - "isoCode": "10", - "name": "Hlavni mesto Praha" - } - ] - }, - "timestamp": 1655306913479, - "time": "2022-06-15T15:28:33Z", - "url": "https://fingerprint.com/", - "tag": {}, - "confidence": { - "score": 0.99 - }, - "visitorFound": true, - "firstSeenAt": { - "global": "2022-02-04T11:31:20Z", - "subscription": "2022-02-04T11:31:20Z" - }, - "lastSeenAt": { - "global": "2022-06-15T08:47:34.677Z", - "subscription": "2022-06-15T08:47:34.677Z" - } - }, - { - "requestId": "1655282854672.vz4ZlN", - "browserDetails": { - "browserName": "Chrome", - "browserMajorVersion": "102", - "browserFullVersion": "102.0.5005", - "os": "Mac OS X", - "osVersion": "10.15.7", - "device": "Other", - "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.61 Safari/537.36" - }, - "incognito": false, - "ip": "82.118.30.91", - "ipLocation": { - "accuracyRadius": 1000, - "latitude": 50.0805, - "longitude": 14.467, - "postalCode": "130 00", - "timezone": "Europe/Prague", - "city": { - "name": "Prague" - }, - "country": { - "code": "CZ", - "name": "Czechia" - }, - "continent": { - "code": "EU", - "name": "Europe" - }, - "subdivisions": [ - { - "isoCode": "10", - "name": "Hlavni mesto Praha" - } - ] - }, - "timestamp": 1655282854677, - "time": "2022-06-15T08:47:34Z", - "url": "https://fingerprint.com/", - "tag": {}, - "confidence": { - "score": 0.99 - }, - "visitorFound": true, - "firstSeenAt": { - "global": "2022-02-04T11:31:20Z", - "subscription": "2022-02-04T11:31:20Z" - }, - "lastSeenAt": { - "global": "2022-06-14T14:19:42.753Z", - "subscription": "2022-06-14T14:19:42.753Z" - } - }, - { - "requestId": "1655216382743.RDRa4h", - "browserDetails": { - "browserName": "Chrome", - "browserMajorVersion": "102", - "browserFullVersion": "102.0.5005", - "os": "Mac OS X", - "osVersion": "10.15.7", - "device": "Other", - "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.61 Safari/537.36" - }, - "incognito": false, - "ip": "188.242.36.107", - "ipLocation": { - "accuracyRadius": 5, - "latitude": 59.8983, - "longitude": 30.2618, - "postalCode": "190924", - "timezone": "Europe/Moscow", - "city": { - "name": "St Petersburg" - }, - "country": { - "code": "RU", - "name": "Russia" - }, - "continent": { - "code": "EU", - "name": "Europe" - }, - "subdivisions": [ - { - "isoCode": "SPE", - "name": "St.-Petersburg" - } - ] - }, - "timestamp": 1655216382753, - "time": "2022-06-14T14:19:42Z", - "url": "https://fingerprint.com/", - "tag": {}, - "confidence": { - "score": 1 - }, - "visitorFound": true, - "firstSeenAt": { - "global": "2022-02-04T11:31:20Z", - "subscription": "2022-02-04T11:31:20Z" - }, - "lastSeenAt": { - "global": "2022-06-13T07:53:19.878Z", - "subscription": "2022-06-13T07:53:19.878Z" - } - }, - { - "requestId": "1655106799870.C8m8hR", - "browserDetails": { - "browserName": "Chrome", - "browserMajorVersion": "102", - "browserFullVersion": "102.0.5005", - "os": "Mac OS X", - "osVersion": "10.15.7", - "device": "Other", - "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.61 Safari/537.36" - }, - "incognito": false, - "ip": "45.86.200.137", - "timestamp": 1655106799878, - "time": "2022-06-13T07:53:19Z", - "url": "https://fingerprint.com/", - "tag": {}, - "confidence": { - "score": 0.99 - }, - "visitorFound": true, - "firstSeenAt": { - "global": "2022-02-04T11:31:20Z", - "subscription": "2022-02-04T11:31:20Z" - }, - "lastSeenAt": { - "global": "2022-06-07T12:54:35.413Z", - "subscription": "2022-06-07T12:54:35.413Z" - } - }, - { - "requestId": "1654606475406.2uXCJx", - "browserDetails": { - "browserName": "Chrome", - "browserMajorVersion": "102", - "browserFullVersion": "102.0.5005", - "os": "Mac OS X", - "osVersion": "10.15.7", - "device": "Other", - "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.61 Safari/537.36" - }, - "incognito": false, - "ip": "89.38.224.157", - "ipLocation": { - "accuracyRadius": 20, - "latitude": 44.804, - "longitude": 20.4651 - }, - "timestamp": 1654606475413, - "time": "2022-06-07T12:54:35Z", - "url": "https://fingerprintjs.com/", - "tag": {}, - "confidence": { - "score": 0.99 - }, - "visitorFound": true, - "firstSeenAt": { - "global": "2022-02-04T11:31:20Z", - "subscription": "2022-02-04T11:31:20Z" - }, - "lastSeenAt": { - "global": "2022-06-07T09:37:57.43Z", - "subscription": "2022-06-07T09:37:57.43Z" - } - }, - { - "requestId": "1654594677423.pCHmKJ", - "browserDetails": { - "browserName": "Chrome", - "browserMajorVersion": "102", - "browserFullVersion": "102.0.5005", - "os": "Mac OS X", - "osVersion": "10.15.7", - "device": "Other", - "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.61 Safari/537.36" - }, - "incognito": false, - "ip": "188.242.36.107", - "ipLocation": { - "timezone": "Europe/Moscow" - }, - "timestamp": 1654594677430, - "time": "2022-06-07T09:37:57Z", - "url": "https://fingerprintjs.com/", - "tag": {}, - "confidence": { - "score": 0.99 - }, - "visitorFound": true, - "firstSeenAt": { - "global": "2022-02-04T11:31:20Z", - "subscription": "2022-02-04T11:31:20Z" - }, - "lastSeenAt": { - "global": "2022-06-07T09:37:50.109Z", - "subscription": "2022-06-07T09:37:50.109Z" - } - }, - { - "requestId": "1654594670097.Lmodmj", - "browserDetails": { - "browserName": "Chrome", - "browserMajorVersion": "102", - "browserFullVersion": "102.0.5005", - "os": "Mac OS X", - "osVersion": "10.15.7", - "device": "Other", - "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.61 Safari/537.36" - }, - "incognito": false, - "ip": "188.242.36.107", - "ipLocation": { - "accuracyRadius": 5, - "latitude": 59.8983, - "longitude": 30.2618, - "postalCode": "190924", - "timezone": "Europe/Moscow", - "city": { - "name": "St Petersburg" - }, - "country": { - "code": "RU", - "name": "Russia" - }, - "continent": { - "code": "EU", - "name": "Europe" - }, - "subdivisions": [ - { - "isoCode": "SPE", - "name": "St.-Petersburg" - } - ] - }, - "timestamp": 1654594670109, - "time": "2022-06-07T09:37:50Z", - "url": "https://fingerprintjs.com/", - "tag": {}, - "confidence": { - "score": 0.99 - }, - "visitorFound": true, - "firstSeenAt": { - "global": "2022-02-04T11:31:20Z", - "subscription": "2022-02-04T11:31:20Z" - }, - "lastSeenAt": { - "global": "2022-06-07T08:31:31.9Z", - "subscription": "2022-06-07T08:31:31.9Z" - } - }, - { - "requestId": "1654590691894.aCYqYE", - "browserDetails": { - "browserName": "Chrome", - "browserMajorVersion": "102", - "browserFullVersion": "102.0.5005", - "os": "Mac OS X", - "osVersion": "10.15.7", - "device": "Other", - "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.61 Safari/537.36" - }, - "incognito": false, - "ip": "188.242.36.107", - "ipLocation": { - "accuracyRadius": 5, - "latitude": 59.8983, - "longitude": 30.2618, - "postalCode": "190924", - "timezone": "Europe/Moscow", - "city": { - "name": "St Petersburg" - }, - "country": { - "code": "RU", - "name": "Russia" - }, - "continent": { - "code": "EU", - "name": "Europe" - }, - "subdivisions": [ - { - "isoCode": "SPE", - "name": "St.-Petersburg" - } - ] - }, - "timestamp": 1654590691900, - "time": "2022-06-07T08:31:31Z", - "url": "https://fingerprintjs.com/", - "tag": {}, - "confidence": { - "score": 0.99 - }, - "visitorFound": true, - "firstSeenAt": { - "global": "2022-02-04T11:31:20Z", - "subscription": "2022-02-04T11:31:20Z" - }, - "lastSeenAt": { - "global": "2022-06-06T09:05:25.954Z", - "subscription": "2022-06-06T09:05:25.954Z" - } - }, - { - "requestId": "1654506325946.ijIwzu", - "browserDetails": { - "browserName": "Chrome", - "browserMajorVersion": "102", - "browserFullVersion": "102.0.5005", - "os": "Mac OS X", - "osVersion": "10.15.7", - "device": "Other", - "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.61 Safari/537.36" - }, - "incognito": false, - "ip": "89.38.224.165", - "ipLocation": { - "accuracyRadius": 20, - "latitude": 44.804, - "longitude": 20.4651, - "timezone": "Europe/Belgrade", - "city": { - "name": "Belgrade" - }, - "country": { - "code": "RS", - "name": "Serbia" - }, - "continent": { - "code": "EU", - "name": "Europe" - }, - "subdivisions": [ - { - "isoCode": "00", - "name": "Belgrade" - } - ] - }, - "timestamp": 1654506325954, - "time": "2022-06-06T09:05:25Z", - "url": "https://fingerprintcom.netlify.app/blog/name-change/", - "tag": {}, - "confidence": { - "score": 0.99 - }, - "visitorFound": true, - "firstSeenAt": { - "global": "2022-02-04T11:31:20Z", - "subscription": "2022-02-04T11:31:20Z" - }, - "lastSeenAt": { - "global": "2022-06-02T16:58:53.635Z", - "subscription": "2022-06-02T16:58:53.635Z" - } - }, - { - "requestId": "1654189133629.0V1gtF", - "browserDetails": { - "browserName": "Chrome", - "browserMajorVersion": "102", - "browserFullVersion": "102.0.5005", - "os": "Mac OS X", - "osVersion": "10.15.7", - "device": "Other", - "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.61 Safari/537.36" - }, - "incognito": false, - "ip": "89.38.224.165", - "ipLocation": { - "accuracyRadius": 20, - "latitude": 44.804, - "longitude": 20.4651, - "timezone": "Europe/Belgrade", - "city": { - "name": "Belgrade" - }, - "country": { - "code": "RS", - "name": "Serbia" - }, - "continent": { - "code": "EU", - "name": "Europe" - }, - "subdivisions": [ - { - "isoCode": "00", - "name": "Belgrade" - } - ] - }, - "timestamp": 1654189133635, - "time": "2022-06-02T16:58:53Z", - "url": "https://fingerprintcom.netlify.app/blog/name-change/", - "tag": {}, - "confidence": { - "score": 1 - }, - "visitorFound": true, - "firstSeenAt": { - "global": "2022-02-04T11:31:20Z", - "subscription": "2022-02-04T11:31:20Z" - }, - "lastSeenAt": { - "global": "2022-06-02T16:58:51.483Z", - "subscription": "2022-06-02T16:58:51.483Z" - } - }, - { - "requestId": "1654189131472.r49Bbh", - "browserDetails": { - "browserName": "Chrome", - "browserMajorVersion": "102", - "browserFullVersion": "102.0.5005", - "os": "Mac OS X", - "osVersion": "10.15.7", - "device": "Other", - "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.61 Safari/537.36" - }, - "incognito": false, - "ip": "89.38.224.165", - "ipLocation": { - "accuracyRadius": 20, - "latitude": 44.804, - "longitude": 20.4651, - "timezone": "Europe/Belgrade", - "city": { - "name": "Belgrade" - }, - "country": { - "code": "RS", - "name": "Serbia" - }, - "continent": { - "code": "EU", - "name": "Europe" - }, - "subdivisions": [ - { - "isoCode": "00", - "name": "Belgrade" - } - ] - }, - "timestamp": 1654189131483, - "time": "2022-06-02T16:58:51Z", - "url": "https://fingerprintcom.netlify.app/", - "tag": {}, - "confidence": { - "score": 0.95 - }, - "visitorFound": true, - "firstSeenAt": { - "global": "2022-02-04T11:31:20Z", - "subscription": "2022-02-04T11:31:20Z" - }, - "lastSeenAt": { - "global": "2022-05-27T14:52:26.624Z", - "subscription": "2022-05-27T14:52:26.624Z" - } - }, - { - "requestId": "1653663146617.o8KpJO", - "browserDetails": { - "browserName": "Chrome", - "browserMajorVersion": "101", - "browserFullVersion": "101.0.4951", - "os": "Mac OS X", - "osVersion": "10.15.7", - "device": "Other", - "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.64 Safari/537.36" - }, - "incognito": false, - "ip": "89.38.224.165", - "ipLocation": { - "accuracyRadius": 20, - "latitude": 44.804, - "longitude": 20.4651, - "timezone": "Europe/Belgrade", - "city": { - "name": "Belgrade" - }, - "country": { - "code": "RS", - "name": "Serbia" - }, - "continent": { - "code": "EU", - "name": "Europe" - }, - "subdivisions": [ - { - "isoCode": "00", - "name": "Belgrade" - } - ] - }, - "timestamp": 1653663146624, - "time": "2022-05-27T14:52:26Z", - "url": "https://fingerprintjs.com/", - "tag": {}, - "confidence": { - "score": 1 - }, - "visitorFound": true, - "firstSeenAt": { - "global": "2022-02-04T11:31:20Z", - "subscription": "2022-02-04T11:31:20Z" - }, - "lastSeenAt": { - "global": "2022-05-20T09:50:06.7Z", - "subscription": "2022-05-20T09:50:06.7Z" - } - }, - { - "requestId": "1653040206694.Q5Csig", - "browserDetails": { - "browserName": "Chrome", - "browserMajorVersion": "101", - "browserFullVersion": "101.0.4951", - "os": "Mac OS X", - "osVersion": "10.15.7", - "device": "Other", - "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.54 Safari/537.36" - }, - "incognito": false, - "ip": "89.38.224.165", - "ipLocation": { - "accuracyRadius": 20, - "latitude": 44.804, - "longitude": 20.4651, - "timezone": "Europe/Belgrade", - "city": { - "name": "Belgrade" - }, - "country": { - "code": "RS", - "name": "Serbia" - }, - "continent": { - "code": "EU", - "name": "Europe" - }, - "subdivisions": [ - { - "isoCode": "00", - "name": "Belgrade" - } - ] - }, - "timestamp": 1653040206700, - "time": "2022-05-20T09:50:06Z", - "url": "https://fingerprintjs.com/", - "tag": {}, - "confidence": { - "score": 1 - }, - "visitorFound": true, - "firstSeenAt": { - "global": "2022-02-04T11:31:20Z", - "subscription": "2022-02-04T11:31:20Z" - }, - "lastSeenAt": { - "global": "2022-05-19T16:27:38.029Z", - "subscription": "2022-05-19T16:27:38.029Z" - } - }, - { - "requestId": "1652977658020.xbfYhA", - "browserDetails": { - "browserName": "Chrome", - "browserMajorVersion": "101", - "browserFullVersion": "101.0.4951", - "os": "Mac OS X", - "osVersion": "10.15.7", - "device": "Other", - "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.54 Safari/537.36" - }, - "incognito": false, - "ip": "89.38.224.165", - "ipLocation": { - "accuracyRadius": 20, - "latitude": 44.804, - "longitude": 20.4651, - "timezone": "Europe/Belgrade", - "city": { - "name": "Belgrade" - }, - "country": { - "code": "RS", - "name": "Serbia" - }, - "continent": { - "code": "EU", - "name": "Europe" - }, - "subdivisions": [ - { - "isoCode": "00", - "name": "Belgrade" - } - ] - }, - "timestamp": 1652977658029, - "time": "2022-05-19T16:27:38Z", - "url": "https://fingerprintjs.com/", - "tag": {}, - "confidence": { - "score": 1 - }, - "visitorFound": true, - "firstSeenAt": { - "global": "2022-02-04T11:31:20Z", - "subscription": "2022-02-04T11:31:20Z" - }, - "lastSeenAt": { - "global": "2022-05-17T15:09:32.666Z", - "subscription": "2022-05-17T15:09:32.666Z" - } - }, - { - "requestId": "1652800172657.xA22Pd", - "browserDetails": { - "browserName": "Chrome", - "browserMajorVersion": "101", - "browserFullVersion": "101.0.4951", - "os": "Mac OS X", - "osVersion": "10.15.7", - "device": "Other", - "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.54 Safari/537.36" - }, - "incognito": false, - "ip": "89.38.224.165", - "ipLocation": { - "accuracyRadius": 20, - "latitude": 44.804, - "longitude": 20.4651, - "timezone": "Europe/Belgrade", - "city": { - "name": "Belgrade" - }, - "country": { - "code": "RS", - "name": "Serbia" - }, - "continent": { - "code": "EU", - "name": "Europe" - }, - "subdivisions": [ - { - "isoCode": "00", - "name": "Belgrade" - } - ] - }, - "timestamp": 1652800172666, - "time": "2022-05-17T15:09:32Z", - "url": "https://fingerprintjs.com/", - "tag": {}, - "confidence": { - "score": 1 - }, - "visitorFound": true, - "firstSeenAt": { - "global": "2022-02-04T11:31:20Z", - "subscription": "2022-02-04T11:31:20Z" - }, - "lastSeenAt": { - "global": "2022-05-17T14:18:17.631Z", - "subscription": "2022-05-17T14:18:17.631Z" - } - }, - { - "requestId": "1652797097626.faAMJO", - "browserDetails": { - "browserName": "Chrome", - "browserMajorVersion": "101", - "browserFullVersion": "101.0.4951", - "os": "Mac OS X", - "osVersion": "10.15.7", - "device": "Other", - "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.54 Safari/537.36" - }, - "incognito": false, - "ip": "89.38.224.165", - "ipLocation": { - "accuracyRadius": 20, - "latitude": 44.804, - "longitude": 20.4651, - "timezone": "Europe/Belgrade", - "city": { - "name": "Belgrade" - }, - "country": { - "code": "RS", - "name": "Serbia" - }, - "continent": { - "code": "EU", - "name": "Europe" - }, - "subdivisions": [ - { - "isoCode": "00", - "name": "Belgrade" - } - ] - }, - "timestamp": 1652797097631, - "time": "2022-05-17T14:18:17Z", - "url": "https://fingerprintjs.com/careers/", - "tag": {}, - "confidence": { - "score": 1 - }, - "visitorFound": true, - "firstSeenAt": { - "global": "2022-02-04T11:31:20Z", - "subscription": "2022-02-04T11:31:20Z" - }, - "lastSeenAt": { - "global": "2022-05-17T10:16:04.809Z", - "subscription": "2022-05-17T10:16:04.809Z" - } - }, - { - "requestId": "1652782564800.MWH0GO", - "browserDetails": { - "browserName": "Chrome", - "browserMajorVersion": "101", - "browserFullVersion": "101.0.4951", - "os": "Mac OS X", - "osVersion": "10.15.7", - "device": "Other", - "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.54 Safari/537.36" - }, - "incognito": false, - "ip": "89.38.224.165", - "ipLocation": { - "accuracyRadius": 20, - "latitude": 44.804, - "longitude": 20.4651, - "timezone": "Europe/Belgrade", - "city": { - "name": "Belgrade" - }, - "country": { - "code": "RS", - "name": "Serbia" - }, - "continent": { - "code": "EU", - "name": "Europe" - }, - "subdivisions": [ - { - "isoCode": "00", - "name": "Belgrade" - } - ] - }, - "timestamp": 1652782564809, - "time": "2022-05-17T10:16:04Z", - "url": "https://fingerprintjs.com/", - "tag": {}, - "confidence": { - "score": 1 - }, - "visitorFound": true, - "firstSeenAt": { - "global": "2022-02-04T11:31:20Z", - "subscription": "2022-02-04T11:31:20Z" - }, - "lastSeenAt": { - "global": "2022-05-16T06:47:01.511Z", - "subscription": "2022-05-16T06:47:01.511Z" - } - }, - { - "requestId": "1652683621505.1tOjuc", - "browserDetails": { - "browserName": "Chrome", - "browserMajorVersion": "101", - "browserFullVersion": "101.0.4951", - "os": "Mac OS X", - "osVersion": "10.15.7", - "device": "Other", - "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.54 Safari/537.36" - }, - "incognito": false, - "ip": "217.150.54.233", - "ipLocation": { - "accuracyRadius": 1000, - "latitude": 59.8983, - "longitude": 30.2618, - "postalCode": "190924", - "timezone": "Europe/Moscow", - "city": { - "name": "St Petersburg" - }, - "country": { - "code": "RU", - "name": "Russia" - }, - "continent": { - "code": "EU", - "name": "Europe" - }, - "subdivisions": [ - { - "isoCode": "SPE", - "name": "St.-Petersburg" - } - ] - }, - "timestamp": 1652683621511, - "time": "2022-05-16T06:47:01Z", - "url": "https://fingerprintjs.com/products/bot-detection/", - "tag": {}, - "confidence": { - "score": 1 - }, - "visitorFound": true, - "firstSeenAt": { - "global": "2022-02-04T11:31:20Z", - "subscription": "2022-02-04T11:31:20Z" - }, - "lastSeenAt": { - "global": "2022-05-16T06:45:49.586Z", - "subscription": "2022-05-16T06:45:49.586Z" - } - }, - { - "requestId": "1652683586557.67Faeg", - "browserDetails": { - "browserName": "Chrome", - "browserMajorVersion": "101", - "browserFullVersion": "101.0.4951", - "os": "Mac OS X", - "osVersion": "10.15.7", - "device": "Other", - "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.54 Safari/537.36" - }, - "incognito": true, - "ip": "217.150.54.233", - "ipLocation": { - "accuracyRadius": 1000, - "latitude": 59.8983, - "longitude": 30.2618, - "postalCode": "190924", - "timezone": "Europe/Moscow", - "city": { - "name": "St Petersburg" - }, - "country": { - "code": "RU", - "name": "Russia" - }, - "continent": { - "code": "EU", - "name": "Europe" - }, - "subdivisions": [ - { - "isoCode": "SPE", - "name": "St.-Petersburg" - } - ] - }, - "timestamp": 1652683586562, - "time": "2022-05-16T06:46:26Z", - "url": "https://fingerprintjs.com/", - "tag": {}, - "confidence": { - "score": 0.94 - }, - "visitorFound": true, - "firstSeenAt": { - "global": "2022-02-04T11:31:20Z", - "subscription": "2022-02-04T11:31:20Z" - }, - "lastSeenAt": { - "global": "2022-05-16T06:45:49.586Z", - "subscription": "2022-05-16T06:45:49.586Z" - } - }, - { - "requestId": "1652683549513.aVRqEP", - "browserDetails": { - "browserName": "Chrome", - "browserMajorVersion": "101", - "browserFullVersion": "101.0.4951", - "os": "Mac OS X", - "osVersion": "10.15.7", - "device": "Other", - "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.54 Safari/537.36" - }, - "incognito": false, - "ip": "217.150.54.233", - "ipLocation": { - "accuracyRadius": 1000, - "latitude": 59.8983, - "longitude": 30.2618, - "postalCode": "190924", - "timezone": "Europe/Moscow", - "city": { - "name": "St Petersburg" - }, - "country": { - "code": "RU", - "name": "Russia" - }, - "continent": { - "code": "EU", - "name": "Europe" - }, - "subdivisions": [ - { - "isoCode": "SPE", - "name": "St.-Petersburg" - } - ] - }, - "timestamp": 1652683549586, - "time": "2022-05-16T06:45:49Z", - "url": "https://fingerprintjs.com/", - "tag": {}, - "confidence": { - "score": 1 - }, - "visitorFound": true, - "firstSeenAt": { - "global": "2022-02-04T11:31:20Z", - "subscription": "2022-02-04T11:31:20Z" - }, - "lastSeenAt": { - "global": "2022-05-05T10:11:25.96Z", - "subscription": "2022-05-05T10:11:25.96Z" - } - }, - { - "requestId": "1651745485951.Oj68me", - "browserDetails": { - "browserName": "Chrome", - "browserMajorVersion": "100", - "browserFullVersion": "100.0.4896", - "os": "Mac OS X", - "osVersion": "10.15.7", - "device": "Other", - "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36" - }, - "incognito": false, - "ip": "89.38.224.165", - "ipLocation": { - "accuracyRadius": 20, - "latitude": 44.804, - "longitude": 20.4651, - "timezone": "Europe/Belgrade", - "city": { - "name": "Belgrade" - }, - "country": { - "code": "RS", - "name": "Serbia" - }, - "continent": { - "code": "EU", - "name": "Europe" - }, - "subdivisions": [ - { - "isoCode": "00", - "name": "Belgrade" - } - ] - }, - "timestamp": 1651745485960, - "time": "2022-05-05T10:11:25Z", - "url": "https://fingerprintjs.com/", - "tag": {}, - "confidence": { - "score": 1 - }, - "visitorFound": true, - "firstSeenAt": { - "global": "2022-02-04T11:31:20Z", - "subscription": "2022-02-04T11:31:20Z" - }, - "lastSeenAt": { - "global": "2022-05-05T09:33:40.155Z", - "subscription": "2022-05-05T09:33:40.155Z" - } - }, - { - "requestId": "1651743220004.W02rhx", - "browserDetails": { - "browserName": "Chrome", - "browserMajorVersion": "100", - "browserFullVersion": "100.0.4896", - "os": "Mac OS X", - "osVersion": "10.15.7", - "device": "Other", - "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36" - }, - "incognito": false, - "ip": "89.38.224.165", - "ipLocation": { - "accuracyRadius": 20, - "latitude": 44.804, - "longitude": 20.4651, - "timezone": "Europe/Belgrade", - "city": { - "name": "Belgrade" - }, - "country": { - "code": "RS", - "name": "Serbia" - }, - "continent": { - "code": "EU", - "name": "Europe" - }, - "subdivisions": [ - { - "isoCode": "00", - "name": "Belgrade" - } - ] - }, - "timestamp": 1651743220155, - "time": "2022-05-05T09:33:40Z", - "url": "https://fingerprintjs.com/", - "tag": {}, - "confidence": { - "score": 1 - }, - "visitorFound": true, - "firstSeenAt": { - "global": "2022-02-04T11:31:20Z", - "subscription": "2022-02-04T11:31:20Z" - }, - "lastSeenAt": { - "global": "2022-05-03T15:26:32.826Z", - "subscription": "2022-05-03T15:26:32.826Z" - } - }, - { - "requestId": "1651591592822.Is9u93", - "browserDetails": { - "browserName": "Chrome", - "browserMajorVersion": "100", - "browserFullVersion": "100.0.4896", - "os": "Mac OS X", - "osVersion": "10.15.7", - "device": "Other", - "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36" - }, - "incognito": false, - "ip": "89.38.224.157", - "ipLocation": { - "accuracyRadius": 20, - "latitude": 44.804, - "longitude": 20.4651, - "timezone": "Europe/Belgrade", - "city": { - "name": "Belgrade" - }, - "country": { - "code": "RS", - "name": "Serbia" - }, - "continent": { - "code": "EU", - "name": "Europe" - }, - "subdivisions": [ - { - "isoCode": "00", - "name": "Belgrade" - } - ] - }, - "timestamp": 1651591592826, - "time": "2022-05-03T15:26:32Z", - "url": "https://fingerprintjs.com/", - "tag": {}, - "confidence": { - "score": 1 - }, - "visitorFound": true, - "firstSeenAt": { - "global": "2022-02-04T11:31:20Z", - "subscription": "2022-02-04T11:31:20Z" - }, - "lastSeenAt": { - "global": "2022-04-29T13:23:37.049Z", - "subscription": "2022-04-29T13:23:37.049Z" - } - }, - { - "requestId": "1651238617044.rMVPGS", - "browserDetails": { - "browserName": "Chrome", - "browserMajorVersion": "100", - "browserFullVersion": "100.0.4896", - "os": "Mac OS X", - "osVersion": "10.15.7", - "device": "Other", - "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36" - }, - "incognito": false, - "ip": "89.38.224.165", - "ipLocation": { - "accuracyRadius": 20, - "latitude": 44.804, - "longitude": 20.4651, - "timezone": "Europe/Belgrade", - "city": { - "name": "Belgrade" - }, - "country": { - "code": "RS", - "name": "Serbia" - }, - "continent": { - "code": "EU", - "name": "Europe" - }, - "subdivisions": [ - { - "isoCode": "00", - "name": "Belgrade" - } - ] - }, - "timestamp": 1651238617049, - "time": "2022-04-29T13:23:37Z", - "url": "https://fingerprintjs.com/", - "tag": {}, - "confidence": { - "score": 1 - }, - "visitorFound": true, - "firstSeenAt": { - "global": "2022-02-04T11:31:20Z", - "subscription": "2022-02-04T11:31:20Z" - }, - "lastSeenAt": { - "global": "2022-04-29T10:37:53.333Z", - "subscription": "2022-04-29T10:37:53.333Z" - } - }, - { - "requestId": "1651228673329.QZI2Cu", - "browserDetails": { - "browserName": "Chrome", - "browserMajorVersion": "100", - "browserFullVersion": "100.0.4896", - "os": "Mac OS X", - "osVersion": "10.15.7", - "device": "Other", - "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36" - }, - "incognito": false, - "ip": "188.242.36.107", - "ipLocation": { - "accuracyRadius": 5, - "latitude": 59.8983, - "longitude": 30.2618, - "postalCode": "190924", - "timezone": "Europe/Moscow", - "city": { - "name": "St Petersburg" - }, - "country": { - "code": "RU", - "name": "Russia" - }, - "continent": { - "code": "EU", - "name": "Europe" - }, - "subdivisions": [ - { - "isoCode": "SPE", - "name": "St.-Petersburg" - } - ] - }, - "timestamp": 1651228673333, - "time": "2022-04-29T10:37:53Z", - "url": "https://fingerprintjs.com/", - "tag": {}, - "confidence": { - "score": 1 - }, - "visitorFound": true, - "firstSeenAt": { - "global": "2022-02-04T11:31:20Z", - "subscription": "2022-02-04T11:31:20Z" - }, - "lastSeenAt": { - "global": "2022-04-28T13:58:06.323Z", - "subscription": "2022-04-28T13:58:06.323Z" - } - }, - { - "requestId": "1651154286221.YvuOCP", - "browserDetails": { - "browserName": "Chrome", - "browserMajorVersion": "100", - "browserFullVersion": "100.0.4896", - "os": "Mac OS X", - "osVersion": "10.15.7", - "device": "Other", - "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36" - }, - "incognito": false, - "ip": "84.247.59.113", - "ipLocation": { - "accuracyRadius": 20, - "latitude": 50.0971, - "longitude": 8.5952, - "postalCode": "65933", - "timezone": "Europe/Berlin", - "city": { - "name": "Frankfurt am Main" - }, - "country": { - "code": "DE", - "name": "Germany" - }, - "continent": { - "code": "EU", - "name": "Europe" - }, - "subdivisions": [ - { - "isoCode": "HE", - "name": "Hesse" - } - ] - }, - "timestamp": 1651154286323, - "time": "2022-04-28T13:58:06Z", - "url": "https://fingerprintjs.com/", - "tag": {}, - "confidence": { - "score": 1 - }, - "visitorFound": true, - "firstSeenAt": { - "global": "2022-02-04T11:31:20Z", - "subscription": "2022-02-04T11:31:20Z" - }, - "lastSeenAt": { - "global": "2022-04-28T12:16:02.564Z", - "subscription": "2022-04-28T12:16:02.564Z" - } - }, - { - "requestId": "1651148162556.dySgif", - "browserDetails": { - "browserName": "Chrome", - "browserMajorVersion": "100", - "browserFullVersion": "100.0.4896", - "os": "Mac OS X", - "osVersion": "10.15.7", - "device": "Other", - "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36" - }, - "incognito": false, - "ip": "84.247.59.113", - "ipLocation": { - "accuracyRadius": 20, - "latitude": 50.0971, - "longitude": 8.5952, - "postalCode": "65933", - "timezone": "Europe/Berlin", - "city": { - "name": "Frankfurt am Main" - }, - "country": { - "code": "DE", - "name": "Germany" - }, - "continent": { - "code": "EU", - "name": "Europe" - }, - "subdivisions": [ - { - "isoCode": "HE", - "name": "Hesse" - } - ] - }, - "timestamp": 1651148162564, - "time": "2022-04-28T12:16:02Z", - "url": "https://fingerprintjs.com/", - "tag": {}, - "confidence": { - "score": 1 - }, - "visitorFound": true, - "firstSeenAt": { - "global": "2022-02-04T11:31:20Z", - "subscription": "2022-02-04T11:31:20Z" - }, - "lastSeenAt": { - "global": "2022-04-28T11:57:13.267Z", - "subscription": "2022-04-28T11:57:13.267Z" - } - }, - { - "requestId": "1651147033260.SxmFvL", - "browserDetails": { - "browserName": "Chrome", - "browserMajorVersion": "100", - "browserFullVersion": "100.0.4896", - "os": "Mac OS X", - "osVersion": "10.15.7", - "device": "Other", - "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36" - }, - "incognito": false, - "ip": "84.247.59.146", - "ipLocation": { - "accuracyRadius": 20, - "latitude": 50.0971, - "longitude": 8.5952, - "postalCode": "65933", - "timezone": "Europe/Berlin", - "city": { - "name": "Frankfurt am Main" - }, - "country": { - "code": "DE", - "name": "Germany" - }, - "continent": { - "code": "EU", - "name": "Europe" - }, - "subdivisions": [ - { - "isoCode": "HE", - "name": "Hesse" - } - ] - }, - "timestamp": 1651147033267, - "time": "2022-04-28T11:57:13Z", - "url": "https://fingerprintjs.com/", - "tag": {}, - "confidence": { - "score": 1 - }, - "visitorFound": true, - "firstSeenAt": { - "global": "2022-02-04T11:31:20Z", - "subscription": "2022-02-04T11:31:20Z" - }, - "lastSeenAt": { - "global": "2022-04-28T11:57:06.24Z", - "subscription": "2022-04-28T11:57:06.24Z" - } - }, - { - "requestId": "1651147026139.aAZ8TO", - "browserDetails": { - "browserName": "Chrome", - "browserMajorVersion": "100", - "browserFullVersion": "100.0.4896", - "os": "Mac OS X", - "osVersion": "10.15.7", - "device": "Other", - "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36" - }, - "incognito": false, - "ip": "84.247.59.146", - "ipLocation": { - "accuracyRadius": 20, - "latitude": 50.0971, - "longitude": 8.5952, - "postalCode": "65933", - "timezone": "Europe/Berlin", - "city": { - "name": "Frankfurt am Main" - }, - "country": { - "code": "DE", - "name": "Germany" - }, - "continent": { - "code": "EU", - "name": "Europe" - }, - "subdivisions": [ - { - "isoCode": "HE", - "name": "Hesse" - } - ] - }, - "timestamp": 1651147026240, - "time": "2022-04-28T11:57:06Z", - "url": "https://fingerprintjs.com/", - "tag": {}, - "confidence": { - "score": 1 - }, - "visitorFound": true, - "firstSeenAt": { - "global": "2022-02-04T11:31:20Z", - "subscription": "2022-02-04T11:31:20Z" - }, - "lastSeenAt": { - "global": "2022-04-26T14:10:31.908Z", - "subscription": "2022-04-26T14:10:31.908Z" - } - }, - { - "requestId": "1650982231903.eG0b6v", - "browserDetails": { - "browserName": "Chrome", - "browserMajorVersion": "100", - "browserFullVersion": "100.0.4896", - "os": "Mac OS X", - "osVersion": "10.15.7", - "device": "Other", - "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36" - }, - "incognito": false, - "ip": "45.86.200.105", - "ipLocation": { - "accuracyRadius": 1000, - "latitude": 52.3824, - "longitude": 4.8995, - "timezone": "Europe/Amsterdam", - "country": { - "code": "NL", - "name": "Netherlands" - }, - "continent": { - "code": "EU", - "name": "Europe" - } - }, - "timestamp": 1650982231908, - "time": "2022-04-26T14:10:31Z", - "url": "https://fingerprintjs.com/", - "tag": {}, - "confidence": { - "score": 1 - }, - "visitorFound": true, - "firstSeenAt": { - "global": "2022-02-04T11:31:20Z", - "subscription": "2022-02-04T11:31:20Z" - }, - "lastSeenAt": { - "global": "2022-04-26T11:43:37.373Z", - "subscription": "2022-04-26T11:43:37.373Z" - } - }, - { - "requestId": "1650973417360.xupFFD", - "browserDetails": { - "browserName": "Chrome", - "browserMajorVersion": "100", - "browserFullVersion": "100.0.4896", - "os": "Mac OS X", - "osVersion": "10.15.7", - "device": "Other", - "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36" - }, - "incognito": false, - "ip": "45.86.200.99", - "ipLocation": { - "accuracyRadius": 1000, - "latitude": 52.3824, - "longitude": 4.8995, - "timezone": "Europe/Amsterdam", - "country": { - "code": "NL", - "name": "Netherlands" - }, - "continent": { - "code": "EU", - "name": "Europe" - } - }, - "timestamp": 1650973417373, - "time": "2022-04-26T11:43:37Z", - "url": "https://fingerprintjs.com/", - "tag": {}, - "confidence": { - "score": 1 - }, - "visitorFound": true, - "firstSeenAt": { - "global": "2022-02-04T11:31:20Z", - "subscription": "2022-02-04T11:31:20Z" - }, - "lastSeenAt": { - "global": "2022-04-26T11:43:30.111Z", - "subscription": "2022-04-26T11:43:30.111Z" - } - }, - { - "requestId": "1650973410104.AQD4qu", - "browserDetails": { - "browserName": "Chrome", - "browserMajorVersion": "100", - "browserFullVersion": "100.0.4896", - "os": "Mac OS X", - "osVersion": "10.15.7", - "device": "Other", - "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36" - }, - "incognito": false, - "ip": "45.86.200.99", - "ipLocation": { - "accuracyRadius": 1000, - "latitude": 52.3824, - "longitude": 4.8995, - "timezone": "Europe/Amsterdam", - "country": { - "code": "NL", - "name": "Netherlands" - }, - "continent": { - "code": "EU", - "name": "Europe" - } - }, - "timestamp": 1650973410111, - "time": "2022-04-26T11:43:30Z", - "url": "https://fingerprintjs.com/", - "tag": {}, - "confidence": { - "score": 1 - }, - "visitorFound": true, - "firstSeenAt": { - "global": "2022-02-04T11:31:20Z", - "subscription": "2022-02-04T11:31:20Z" - }, - "lastSeenAt": { - "global": "2022-04-26T11:22:34.148Z", - "subscription": "2022-04-26T11:22:34.148Z" - } - }, - { - "requestId": "1650972154133.lSWE8a", - "browserDetails": { - "browserName": "Chrome", - "browserMajorVersion": "100", - "browserFullVersion": "100.0.4896", - "os": "Mac OS X", - "osVersion": "10.15.7", - "device": "Other", - "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36" - }, - "incognito": false, - "ip": "45.86.200.96", - "ipLocation": { - "accuracyRadius": 1000, - "latitude": 52.3824, - "longitude": 4.8995, - "timezone": "Europe/Amsterdam", - "country": { - "code": "NL", - "name": "Netherlands" - }, - "continent": { - "code": "EU", - "name": "Europe" - } - }, - "timestamp": 1650972154148, - "time": "2022-04-26T11:22:34Z", - "url": "https://fingerprintjs.com/", - "tag": {}, - "confidence": { - "score": 1 - }, - "visitorFound": true, - "firstSeenAt": { - "global": "2022-02-04T11:31:20Z", - "subscription": "2022-02-04T11:31:20Z" - }, - "lastSeenAt": { - "global": "2022-04-26T11:22:03.83Z", - "subscription": "2022-04-26T11:22:03.83Z" - } - }, - { - "requestId": "1650972123824.xk8MUR", - "browserDetails": { - "browserName": "Chrome", - "browserMajorVersion": "100", - "browserFullVersion": "100.0.4896", - "os": "Mac OS X", - "osVersion": "10.15.7", - "device": "Other", - "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36" - }, - "incognito": false, - "ip": "45.86.200.96", - "ipLocation": { - "accuracyRadius": 1000, - "latitude": 52.3824, - "longitude": 4.8995, - "timezone": "Europe/Amsterdam", - "country": { - "code": "NL", - "name": "Netherlands" - }, - "continent": { - "code": "EU", - "name": "Europe" - } - }, - "timestamp": 1650972123830, - "time": "2022-04-26T11:22:03Z", - "url": "https://fingerprintjs.com/", - "tag": {}, - "confidence": { - "score": 1 - }, - "visitorFound": true, - "firstSeenAt": { - "global": "2022-02-04T11:31:20Z", - "subscription": "2022-02-04T11:31:20Z" - }, - "lastSeenAt": { - "global": "2022-04-25T09:46:15.458Z", - "subscription": "2022-04-25T09:46:15.458Z" - } - }, - { - "requestId": "1650879975452.kfuowM", - "browserDetails": { - "browserName": "Chrome", - "browserMajorVersion": "100", - "browserFullVersion": "100.0.4896", - "os": "Mac OS X", - "osVersion": "10.15.7", - "device": "Other", - "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36" - }, - "incognito": false, - "ip": "188.242.36.107", - "ipLocation": { - "accuracyRadius": 5, - "latitude": 59.8983, - "longitude": 30.2618, - "postalCode": "190924", - "timezone": "Europe/Moscow", - "city": { - "name": "St Petersburg" - }, - "country": { - "code": "RU", - "name": "Russia" - }, - "continent": { - "code": "EU", - "name": "Europe" - }, - "subdivisions": [ - { - "isoCode": "SPE", - "name": "St.-Petersburg" - } - ] - }, - "timestamp": 1650879975458, - "time": "2022-04-25T09:46:15Z", - "url": "https://fingerprintjs.com/", - "tag": {}, - "confidence": { - "score": 1 - }, - "visitorFound": true, - "firstSeenAt": { - "global": "2022-02-04T11:31:20Z", - "subscription": "2022-02-04T11:31:20Z" - }, - "lastSeenAt": { - "global": "2022-04-22T16:51:44.816Z", - "subscription": "2022-04-22T16:51:44.816Z" - } - }, - { - "requestId": "1650646304808.xQbAju", - "browserDetails": { - "browserName": "Chrome", - "browserMajorVersion": "100", - "browserFullVersion": "100.0.4896", - "os": "Mac OS X", - "osVersion": "10.15.7", - "device": "Other", - "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36" - }, - "incognito": false, - "ip": "45.86.200.227", - "ipLocation": { - "accuracyRadius": 1000, - "latitude": 52.3824, - "longitude": 4.8995, - "timezone": "Europe/Amsterdam", - "country": { - "code": "NL", - "name": "Netherlands" - }, - "continent": { - "code": "EU", - "name": "Europe" - } - }, - "timestamp": 1650646304816, - "time": "2022-04-22T16:51:44Z", - "url": "https://fingerprintjs.com/", - "tag": {}, - "confidence": { - "score": 1 - }, - "visitorFound": true, - "firstSeenAt": { - "global": "2022-02-04T11:31:20Z", - "subscription": "2022-02-04T11:31:20Z" - }, - "lastSeenAt": { - "global": "2022-04-21T11:43:33.116Z", - "subscription": "2022-04-21T11:43:33.116Z" - } - }, - { - "requestId": "1650541413105.leAPLz", - "browserDetails": { - "browserName": "Chrome", - "browserMajorVersion": "100", - "browserFullVersion": "100.0.4896", - "os": "Mac OS X", - "osVersion": "10.15.7", - "device": "Other", - "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36" - }, - "incognito": false, - "ip": "45.86.200.89", - "ipLocation": { - "accuracyRadius": 1000, - "latitude": 52.3824, - "longitude": 4.8995, - "timezone": "Europe/Amsterdam", - "country": { - "code": "NL", - "name": "Netherlands" - }, - "continent": { - "code": "EU", - "name": "Europe" - } - }, - "timestamp": 1650541413116, - "time": "2022-04-21T11:43:33Z", - "url": "https://fingerprintjs.com/", - "tag": {}, - "confidence": { - "score": 1 - }, - "visitorFound": true, - "firstSeenAt": { - "global": "2022-02-04T11:31:20Z", - "subscription": "2022-02-04T11:31:20Z" - }, - "lastSeenAt": { - "global": "2022-04-20T17:11:54.717Z", - "subscription": "2022-04-20T17:11:54.717Z" - } - }, - { - "requestId": "1650474714710.M1IGsl", - "browserDetails": { - "browserName": "Chrome", - "browserMajorVersion": "100", - "browserFullVersion": "100.0.4896", - "os": "Mac OS X", - "osVersion": "10.15.7", - "device": "Other", - "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36" - }, - "incognito": false, - "ip": "45.86.200.111", - "ipLocation": { - "accuracyRadius": 1000, - "latitude": 52.3824, - "longitude": 4.8995, - "timezone": "Europe/Amsterdam", - "country": { - "code": "NL", - "name": "Netherlands" - }, - "continent": { - "code": "EU", - "name": "Europe" - } - }, - "timestamp": 1650474714717, - "time": "2022-04-20T17:11:54Z", - "url": "https://fingerprintjs.com/", - "tag": {}, - "confidence": { - "score": 1 - }, - "visitorFound": true, - "firstSeenAt": { - "global": "2022-02-04T11:31:20Z", - "subscription": "2022-02-04T11:31:20Z" - }, - "lastSeenAt": { - "global": "2022-04-20T17:11:47.217Z", - "subscription": "2022-04-20T17:11:47.217Z" - } - }, - { - "requestId": "1650474707211.CEUuZk", - "browserDetails": { - "browserName": "Chrome", - "browserMajorVersion": "100", - "browserFullVersion": "100.0.4896", - "os": "Mac OS X", - "osVersion": "10.15.7", - "device": "Other", - "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36" - }, - "incognito": false, - "ip": "45.86.200.111", - "ipLocation": { - "accuracyRadius": 1000, - "latitude": 52.3824, - "longitude": 4.8995, - "timezone": "Europe/Amsterdam", - "country": { - "code": "NL", - "name": "Netherlands" - }, - "continent": { - "code": "EU", - "name": "Europe" - } - }, - "timestamp": 1650474707217, - "time": "2022-04-20T17:11:47Z", - "url": "https://fingerprintjs.com/", - "tag": {}, - "confidence": { - "score": 1 - }, - "visitorFound": true, - "firstSeenAt": { - "global": "2022-02-04T11:31:20Z", - "subscription": "2022-02-04T11:31:20Z" - }, - "lastSeenAt": { - "global": "2022-04-20T17:11:12.076Z", - "subscription": "2022-04-20T17:11:12.076Z" - } - }, - { - "requestId": "1650474672071.Pz4WsK", - "browserDetails": { - "browserName": "Chrome", - "browserMajorVersion": "100", - "browserFullVersion": "100.0.4896", - "os": "Mac OS X", - "osVersion": "10.15.7", - "device": "Other", - "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36" - }, - "incognito": false, - "ip": "45.86.200.111", - "ipLocation": { - "accuracyRadius": 1000, - "latitude": 52.3824, - "longitude": 4.8995, - "timezone": "Europe/Amsterdam", - "country": { - "code": "NL", - "name": "Netherlands" - }, - "continent": { - "code": "EU", - "name": "Europe" - } - }, - "timestamp": 1650474672076, - "time": "2022-04-20T17:11:12Z", - "url": "https://fingerprintjs.com/", - "tag": {}, - "confidence": { - "score": 1 - }, - "visitorFound": true, - "firstSeenAt": { - "global": "2022-02-04T11:31:20Z", - "subscription": "2022-02-04T11:31:20Z" - }, - "lastSeenAt": { - "global": "2022-04-19T12:29:06.692Z", - "subscription": "2022-04-19T12:29:06.692Z" - } - }, - { - "requestId": "1650371346684.1d7sgv", - "browserDetails": { - "browserName": "Chrome Mobile", - "browserMajorVersion": "100", - "browserFullVersion": "100.0.4896", - "os": "Android", - "osVersion": "6.0", - "device": "Nexus 5", - "userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Mobile Safari/537.36" - }, - "incognito": false, - "ip": "45.86.200.198", - "ipLocation": { - "accuracyRadius": 1000, - "latitude": 52.3824, - "longitude": 4.8995, - "timezone": "Europe/Amsterdam", - "country": { - "code": "NL", - "name": "Netherlands" - }, - "continent": { - "code": "EU", - "name": "Europe" - } - }, - "timestamp": 1650371346692, - "time": "2022-04-19T12:29:06Z", - "url": "https://fingerprintjs.com/", - "tag": {}, - "confidence": { - "score": 1 - }, - "visitorFound": true, - "firstSeenAt": { - "global": "2022-02-04T11:31:20Z", - "subscription": "2022-02-04T11:31:20Z" - }, - "lastSeenAt": { - "global": "2022-04-19T12:29:02.15Z", - "subscription": "2022-04-19T12:29:02.15Z" - } - }, - { - "requestId": "1650371342145.oWyfRx", - "browserDetails": { - "browserName": "Chrome", - "browserMajorVersion": "100", - "browserFullVersion": "100.0.4896", - "os": "Mac OS X", - "osVersion": "10.15.7", - "device": "Other", - "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36" - }, - "incognito": false, - "ip": "45.86.200.198", - "ipLocation": { - "accuracyRadius": 1000, - "latitude": 52.3824, - "longitude": 4.8995, - "timezone": "Europe/Amsterdam", - "country": { - "code": "NL", - "name": "Netherlands" - }, - "continent": { - "code": "EU", - "name": "Europe" - } - }, - "timestamp": 1650371342150, - "time": "2022-04-19T12:29:02Z", - "url": "https://fingerprintjs.com/", - "tag": {}, - "confidence": { - "score": 1 - }, - "visitorFound": true, - "firstSeenAt": { - "global": "2022-02-04T11:31:20Z", - "subscription": "2022-02-04T11:31:20Z" - }, - "lastSeenAt": { - "global": "2022-04-19T11:35:14.729Z", - "subscription": "2022-04-19T11:35:14.729Z" - } - }, - { - "requestId": "1650368114723.YEXcHI", - "browserDetails": { - "browserName": "Chrome", - "browserMajorVersion": "100", - "browserFullVersion": "100.0.4896", - "os": "Mac OS X", - "osVersion": "10.15.7", - "device": "Other", - "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36" - }, - "incognito": false, - "ip": "45.86.200.206", - "ipLocation": { - "accuracyRadius": 1000, - "latitude": 52.3824, - "longitude": 4.8995, - "timezone": "Europe/Amsterdam", - "country": { - "code": "NL", - "name": "Netherlands" - }, - "continent": { - "code": "EU", - "name": "Europe" - } - }, - "timestamp": 1650368114729, - "time": "2022-04-19T11:35:14Z", - "url": "https://fingerprintjs.com/", - "tag": {}, - "confidence": { - "score": 1 - }, - "visitorFound": true, - "firstSeenAt": { - "global": "2022-02-04T11:31:20Z", - "subscription": "2022-02-04T11:31:20Z" - }, - "lastSeenAt": { - "global": "2022-04-19T11:13:33.107Z", - "subscription": "2022-04-19T11:13:33.107Z" - } - }, - { - "requestId": "1650366813101.SvUZC1", - "browserDetails": { - "browserName": "Chrome Mobile", - "browserMajorVersion": "100", - "browserFullVersion": "100.0.4896", - "os": "Android", - "osVersion": "6.0", - "device": "Nexus 5", - "userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Mobile Safari/537.36" - }, - "incognito": false, - "ip": "45.86.200.204", - "ipLocation": { - "accuracyRadius": 1000, - "latitude": 52.3824, - "longitude": 4.8995, - "timezone": "Europe/Amsterdam", - "country": { - "code": "NL", - "name": "Netherlands" - }, - "continent": { - "code": "EU", - "name": "Europe" - } - }, - "timestamp": 1650366813107, - "time": "2022-04-19T11:13:33Z", - "url": "https://fingerprintjs.com/", - "tag": {}, - "confidence": { - "score": 1 - }, - "visitorFound": true, - "firstSeenAt": { - "global": "2022-02-04T11:31:20Z", - "subscription": "2022-02-04T11:31:20Z" - }, - "lastSeenAt": { - "global": "2022-04-19T11:13:28.453Z", - "subscription": "2022-04-19T11:13:28.453Z" - } - }, - { - "requestId": "1650366808426.Hy6j7v", - "browserDetails": { - "browserName": "Chrome", - "browserMajorVersion": "100", - "browserFullVersion": "100.0.4896", - "os": "Mac OS X", - "osVersion": "10.15.7", - "device": "Other", - "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36" - }, - "incognito": false, - "ip": "45.86.200.204", - "ipLocation": { - "accuracyRadius": 1000, - "latitude": 52.3824, - "longitude": 4.8995, - "timezone": "Europe/Amsterdam", - "country": { - "code": "NL", - "name": "Netherlands" - }, - "continent": { - "code": "EU", - "name": "Europe" - } - }, - "timestamp": 1650366808453, - "time": "2022-04-19T11:13:28Z", - "url": "https://fingerprintjs.com/", - "tag": {}, - "confidence": { - "score": 1 - }, - "visitorFound": true, - "firstSeenAt": { - "global": "2022-02-04T11:31:20Z", - "subscription": "2022-02-04T11:31:20Z" - }, - "lastSeenAt": { - "global": "2022-04-19T11:07:05.19Z", - "subscription": "2022-04-19T11:07:05.19Z" - } - }, - { - "requestId": "1650366425184.xvYkdr", - "browserDetails": { - "browserName": "Chrome", - "browserMajorVersion": "100", - "browserFullVersion": "100.0.4896", - "os": "Mac OS X", - "osVersion": "10.15.7", - "device": "Other", - "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36" - }, - "incognito": false, - "ip": "45.86.200.204", - "ipLocation": { - "accuracyRadius": 1000, - "latitude": 52.3824, - "longitude": 4.8995, - "timezone": "Europe/Amsterdam", - "country": { - "code": "NL", - "name": "Netherlands" - }, - "continent": { - "code": "EU", - "name": "Europe" - } - }, - "timestamp": 1650366425190, - "time": "2022-04-19T11:07:05Z", - "url": "https://fingerprintjs.com/", - "tag": {}, - "confidence": { - "score": 1 - }, - "visitorFound": true, - "firstSeenAt": { - "global": "2022-02-04T11:31:20Z", - "subscription": "2022-02-04T11:31:20Z" - }, - "lastSeenAt": { - "global": "2022-04-19T11:07:00.483Z", - "subscription": "2022-04-19T11:07:00.483Z" - } - }, - { - "requestId": "1650366420377.VR5pDX", - "browserDetails": { - "browserName": "Chrome", - "browserMajorVersion": "100", - "browserFullVersion": "100.0.4896", - "os": "Mac OS X", - "osVersion": "10.15.7", - "device": "Other", - "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36" - }, - "incognito": false, - "ip": "45.86.200.204", - "ipLocation": { - "accuracyRadius": 1000, - "latitude": 52.3824, - "longitude": 4.8995, - "timezone": "Europe/Amsterdam", - "country": { - "code": "NL", - "name": "Netherlands" - }, - "continent": { - "code": "EU", - "name": "Europe" - } - }, - "timestamp": 1650366420483, - "time": "2022-04-19T11:07:00Z", - "url": "https://fingerprintjs.com/", - "tag": {}, - "confidence": { - "score": 1 - }, - "visitorFound": true, - "firstSeenAt": { - "global": "2022-02-04T11:31:20Z", - "subscription": "2022-02-04T11:31:20Z" - }, - "lastSeenAt": { - "global": "2022-04-19T10:37:45.279Z", - "subscription": "2022-04-19T10:37:45.279Z" - } - }, - { - "requestId": "1650364665274.qq31O4", - "browserDetails": { - "browserName": "Chrome", - "browserMajorVersion": "100", - "browserFullVersion": "100.0.4896", - "os": "Mac OS X", - "osVersion": "10.15.7", - "device": "Other", - "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36" - }, - "incognito": false, - "ip": "45.86.200.172", - "ipLocation": { - "accuracyRadius": 1000, - "latitude": 52.3824, - "longitude": 4.8995, - "timezone": "Europe/Amsterdam", - "country": { - "code": "NL", - "name": "Netherlands" - }, - "continent": { - "code": "EU", - "name": "Europe" - } - }, - "timestamp": 1650364665279, - "time": "2022-04-19T10:37:45Z", - "url": "https://fingerprintjs.com/", - "tag": {}, - "confidence": { - "score": 1 - }, - "visitorFound": true, - "firstSeenAt": { - "global": "2022-02-04T11:31:20Z", - "subscription": "2022-02-04T11:31:20Z" - }, - "lastSeenAt": { - "global": "2022-04-19T10:22:58.87Z", - "subscription": "2022-04-19T10:22:58.87Z" - } - }, - { - "requestId": "1650363778864.tsVBjO", - "browserDetails": { - "browserName": "Chrome Mobile", - "browserMajorVersion": "100", - "browserFullVersion": "100.0.4896", - "os": "Android", - "osVersion": "6.0", - "device": "Nexus 5", - "userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Mobile Safari/537.36" - }, - "incognito": false, - "ip": "45.86.200.210", - "ipLocation": { - "accuracyRadius": 1000, - "latitude": 52.3824, - "longitude": 4.8995, - "timezone": "Europe/Amsterdam", - "country": { - "code": "NL", - "name": "Netherlands" - }, - "continent": { - "code": "EU", - "name": "Europe" - } - }, - "timestamp": 1650363778870, - "time": "2022-04-19T10:22:58Z", - "url": "https://fingerprintjs.com/", - "tag": {}, - "confidence": { - "score": 1 - }, - "visitorFound": true, - "firstSeenAt": { - "global": "2022-02-04T11:31:20Z", - "subscription": "2022-02-04T11:31:20Z" - }, - "lastSeenAt": { - "global": "2022-04-19T10:22:46.894Z", - "subscription": "2022-04-19T10:22:46.894Z" - } - }, - { - "requestId": "1650363766889.KuVDpm", - "browserDetails": { - "browserName": "Chrome Mobile", - "browserMajorVersion": "100", - "browserFullVersion": "100.0.4896", - "os": "Android", - "osVersion": "6.0", - "device": "Nexus 5", - "userAgent": "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Mobile Safari/537.36" - }, - "incognito": false, - "ip": "45.86.200.210", - "ipLocation": { - "accuracyRadius": 1000, - "latitude": 52.3824, - "longitude": 4.8995, - "timezone": "Europe/Amsterdam", - "country": { - "code": "NL", - "name": "Netherlands" - }, - "continent": { - "code": "EU", - "name": "Europe" - } - }, - "timestamp": 1650363766894, - "time": "2022-04-19T10:22:46Z", - "url": "https://fingerprintjs.com/", - "tag": {}, - "confidence": { - "score": 1 - }, - "visitorFound": true, - "firstSeenAt": { - "global": "2022-02-04T11:31:20Z", - "subscription": "2022-02-04T11:31:20Z" - }, - "lastSeenAt": { - "global": "2022-04-19T10:07:01.528Z", - "subscription": "2022-04-19T10:07:01.528Z" - } - }, - { - "requestId": "1650362821521.dXH2Ce", - "browserDetails": { - "browserName": "Chrome", - "browserMajorVersion": "100", - "browserFullVersion": "100.0.4896", - "os": "Mac OS X", - "osVersion": "10.15.7", - "device": "Other", - "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36" - }, - "incognito": false, - "ip": "45.86.200.180", - "ipLocation": { - "accuracyRadius": 1000, - "latitude": 52.3824, - "longitude": 4.8995, - "timezone": "Europe/Amsterdam", - "country": { - "code": "NL", - "name": "Netherlands" - }, - "continent": { - "code": "EU", - "name": "Europe" - } - }, - "timestamp": 1650362821528, - "time": "2022-04-19T10:07:01Z", - "url": "https://fingerprintjs.com/", - "tag": {}, - "confidence": { - "score": 1 - }, - "visitorFound": true, - "firstSeenAt": { - "global": "2022-02-04T11:31:20Z", - "subscription": "2022-02-04T11:31:20Z" - }, - "lastSeenAt": { - "global": "2022-04-19T10:02:42.46Z", - "subscription": "2022-04-19T10:02:42.46Z" - } - }, - { - "requestId": "1650362562448.a5cPLU", - "browserDetails": { - "browserName": "Chrome", - "browserMajorVersion": "100", - "browserFullVersion": "100.0.4896", - "os": "Mac OS X", - "osVersion": "10.15.7", - "device": "Other", - "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36" - }, - "incognito": false, - "ip": "45.86.200.180", - "ipLocation": { - "accuracyRadius": 1000, - "latitude": 52.3824, - "longitude": 4.8995, - "timezone": "Europe/Amsterdam", - "country": { - "code": "NL", - "name": "Netherlands" - }, - "continent": { - "code": "EU", - "name": "Europe" - } - }, - "timestamp": 1650362562460, - "time": "2022-04-19T10:02:42Z", - "url": "https://fingerprintjs.com/", - "tag": {}, - "confidence": { - "score": 1 - }, - "visitorFound": true, - "firstSeenAt": { - "global": "2022-02-04T11:31:20Z", - "subscription": "2022-02-04T11:31:20Z" - }, - "lastSeenAt": { - "global": "2022-04-18T17:06:30.834Z", - "subscription": "2022-04-18T17:06:30.834Z" - } - }, - { - "requestId": "1650301590829.YXGX7h", - "browserDetails": { - "browserName": "Chrome", - "browserMajorVersion": "100", - "browserFullVersion": "100.0.4896", - "os": "Mac OS X", - "osVersion": "10.15.7", - "device": "Other", - "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.75 Safari/537.36" - }, - "incognito": false, - "ip": "45.86.200.195", - "ipLocation": { - "accuracyRadius": 1000, - "latitude": 52.3824, - "longitude": 4.8995, - "timezone": "Europe/Amsterdam", - "country": { - "code": "NL", - "name": "Netherlands" - }, - "continent": { - "code": "EU", - "name": "Europe" - } - }, - "timestamp": 1650301590834, - "time": "2022-04-18T17:06:30Z", - "url": "https://fingerprintjs.com/", - "tag": {}, - "confidence": { - "score": 1 - }, - "visitorFound": true, - "firstSeenAt": { - "global": "2022-02-04T11:31:20Z", - "subscription": "2022-02-04T11:31:20Z" - }, - "lastSeenAt": { - "global": "2022-04-18T12:23:30.446Z", - "subscription": "2022-04-18T12:23:30.446Z" - } - }, - { - "requestId": "1650284610441.lJrX4M", - "browserDetails": { - "browserName": "Chrome", - "browserMajorVersion": "100", - "browserFullVersion": "100.0.4896", - "os": "Mac OS X", - "osVersion": "10.15.7", - "device": "Other", - "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.75 Safari/537.36" - }, - "incognito": false, - "ip": "45.86.200.179", - "ipLocation": { - "accuracyRadius": 1000, - "latitude": 52.3824, - "longitude": 4.8995, - "timezone": "Europe/Amsterdam", - "country": { - "code": "NL", - "name": "Netherlands" - }, - "continent": { - "code": "EU", - "name": "Europe" - } - }, - "timestamp": 1650284610446, - "time": "2022-04-18T12:23:30Z", - "url": "https://fingerprintjs.com/blog/", - "tag": {}, - "confidence": { - "score": 1 - }, - "visitorFound": true, - "firstSeenAt": { - "global": "2022-02-04T11:31:20Z", - "subscription": "2022-02-04T11:31:20Z" - }, - "lastSeenAt": { - "global": "2022-04-06T14:53:00.526Z", - "subscription": "2022-04-06T14:53:00.526Z" - } - }, - { - "requestId": "1649256780522.WAXWf2", - "browserDetails": { - "browserName": "Chrome", - "browserMajorVersion": "100", - "browserFullVersion": "100.0.4896", - "os": "Mac OS X", - "osVersion": "10.15.7", - "device": "Other", - "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.60 Safari/537.36" - }, - "incognito": false, - "ip": "109.245.35.200", - "ipLocation": { - "accuracyRadius": 50, - "latitude": 44.8166, - "longitude": 20.4721, - "timezone": "Europe/Belgrade", - "city": { - "name": "Belgrade" - }, - "country": { - "code": "RS", - "name": "Serbia" - }, - "continent": { - "code": "EU", - "name": "Europe" - }, - "subdivisions": [ - { - "isoCode": "00", - "name": "Belgrade" - } - ] - }, - "timestamp": 1649256780526, - "time": "2022-04-06T14:53:00Z", - "url": "https://fingerprintjs.com/", - "tag": {}, - "confidence": { - "score": 1 - }, - "visitorFound": true, - "firstSeenAt": { - "global": "2022-02-04T11:31:20Z", - "subscription": "2022-02-04T11:31:20Z" - }, - "lastSeenAt": { - "global": "2022-03-18T11:08:35.698Z", - "subscription": "2022-03-18T11:08:35.698Z" - } - }, - { - "requestId": "1649256780520.RRC4PR", - "browserDetails": { - "browserName": "Chrome", - "browserMajorVersion": "100", - "browserFullVersion": "100.0.4896", - "os": "Mac OS X", - "osVersion": "10.15.7", - "device": "Other", - "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.60 Safari/537.36" - }, - "incognito": false, - "ip": "109.245.35.200", - "ipLocation": { - "accuracyRadius": 50, - "latitude": 44.8166, - "longitude": 20.4721, - "timezone": "Europe/Belgrade", - "city": { - "name": "Belgrade" - }, - "country": { - "code": "RS", - "name": "Serbia" - }, - "continent": { - "code": "EU", - "name": "Europe" - }, - "subdivisions": [ - { - "isoCode": "00", - "name": "Belgrade" - } - ] - }, - "timestamp": 1649256780525, - "time": "2022-04-06T14:53:00Z", - "url": "https://fingerprintjs.com/", - "tag": {}, - "confidence": { - "score": 1 - }, - "visitorFound": true, - "firstSeenAt": { - "global": "2022-02-04T11:31:20Z", - "subscription": "2022-02-04T11:31:20Z" - }, - "lastSeenAt": { - "global": "2022-03-18T11:08:35.698Z", - "subscription": "2022-03-18T11:08:35.698Z" - } - }, - { - "requestId": "1647601715689.iocXfW", - "browserDetails": { - "browserName": "Chrome", - "browserMajorVersion": "98", - "browserFullVersion": "98.0.4758", - "os": "Mac OS X", - "osVersion": "10.15.7", - "device": "Other", - "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.109 Safari/537.36" - }, - "incognito": false, - "ip": "178.223.21.183", - "ipLocation": { - "accuracyRadius": 50, - "latitude": 44.8166, - "longitude": 20.4721, - "timezone": "Europe/Belgrade", - "city": { - "name": "Belgrade" - }, - "country": { - "code": "RS", - "name": "Serbia" - }, - "continent": { - "code": "EU", - "name": "Europe" - }, - "subdivisions": [ - { - "isoCode": "00", - "name": "Belgrade" - } - ] - }, - "timestamp": 1647601715698, - "time": "2022-03-18T11:08:35Z", - "url": "https://fingerprintjs.com/", - "tag": {}, - "confidence": { - "score": 1 - }, - "visitorFound": true, - "firstSeenAt": { - "global": "2022-02-04T11:31:20Z", - "subscription": "2022-02-04T11:31:20Z" - }, - "lastSeenAt": { - "global": "2022-03-16T08:21:23.62Z", - "subscription": "2022-03-16T08:21:23.62Z" - } - }, - { - "requestId": "1647418883615.Vck2NA", - "browserDetails": { - "browserName": "Chrome", - "browserMajorVersion": "98", - "browserFullVersion": "98.0.4758", - "os": "Mac OS X", - "osVersion": "10.15.7", - "device": "Other", - "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.109 Safari/537.36" - }, - "incognito": false, - "ip": "87.116.165.97", - "ipLocation": { - "accuracyRadius": 50, - "latitude": 44.8166, - "longitude": 20.4721, - "timezone": "Europe/Belgrade", - "city": { - "name": "Belgrade" - }, - "country": { - "code": "RS", - "name": "Serbia" - }, - "continent": { - "code": "EU", - "name": "Europe" - }, - "subdivisions": [ - { - "isoCode": "00", - "name": "Belgrade" - } - ] - }, - "timestamp": 1647418883620, - "time": "2022-03-16T08:21:23Z", - "url": "https://fingerprintjs.com/", - "tag": {}, - "confidence": { - "score": 1 - }, - "visitorFound": true, - "firstSeenAt": { - "global": "2022-02-04T11:31:20Z", - "subscription": "2022-02-04T11:31:20Z" - }, - "lastSeenAt": { - "global": "2022-03-16T08:21:18.398Z", - "subscription": "2022-03-16T08:21:18.398Z" - } - }, - { - "requestId": "1647418878391.NZDmht", - "browserDetails": { - "browserName": "Chrome", - "browserMajorVersion": "98", - "browserFullVersion": "98.0.4758", - "os": "Mac OS X", - "osVersion": "10.15.7", - "device": "Other", - "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.109 Safari/537.36" - }, - "incognito": false, - "ip": "87.116.165.97", - "ipLocation": { - "accuracyRadius": 50, - "latitude": 44.8166, - "longitude": 20.4721, - "timezone": "Europe/Belgrade", - "city": { - "name": "Belgrade" - }, - "country": { - "code": "RS", - "name": "Serbia" - }, - "continent": { - "code": "EU", - "name": "Europe" - }, - "subdivisions": [ - { - "isoCode": "00", - "name": "Belgrade" - } - ] - }, - "timestamp": 1647418878398, - "time": "2022-03-16T08:21:18Z", - "url": "https://fingerprintjs.com/", - "tag": {}, - "confidence": { - "score": 1 - }, - "visitorFound": true, - "firstSeenAt": { - "global": "2022-02-04T11:31:20Z", - "subscription": "2022-02-04T11:31:20Z" - }, - "lastSeenAt": { - "global": "2022-03-15T11:46:51.858Z", - "subscription": "2022-03-15T11:46:51.858Z" - } - }, - { - "requestId": "1647344811836.RvNkL5", - "browserDetails": { - "browserName": "Chrome", - "browserMajorVersion": "98", - "browserFullVersion": "98.0.4758", - "os": "Mac OS X", - "osVersion": "10.15.7", - "device": "Other", - "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.109 Safari/537.36" - }, - "incognito": false, - "ip": "87.116.165.97", - "ipLocation": { - "accuracyRadius": 50, - "latitude": 44.8166, - "longitude": 20.4721, - "timezone": "Europe/Belgrade", - "city": { - "name": "Belgrade" - }, - "country": { - "code": "RS", - "name": "Serbia" - }, - "continent": { - "code": "EU", - "name": "Europe" - }, - "subdivisions": [ - { - "isoCode": "00", - "name": "Belgrade" - } - ] - }, - "timestamp": 1647344811858, - "time": "2022-03-15T11:46:51Z", - "url": "https://fingerprintjs.com/", - "tag": {}, - "confidence": { - "score": 1 - }, - "visitorFound": true, - "firstSeenAt": { - "global": "2022-02-04T11:31:20Z", - "subscription": "2022-02-04T11:31:20Z" - }, - "lastSeenAt": { - "global": "2022-03-08T12:33:05.677Z", - "subscription": "2022-03-08T12:33:05.677Z" - } - } - ] -} diff --git a/tests/mocked-responses-tests/mocked-responses-data/get_visitors_400_bad_request.json b/tests/mocked-responses-tests/mocked-responses-data/get_visitors_400_bad_request.json deleted file mode 100644 index c2b6e295..00000000 --- a/tests/mocked-responses-tests/mocked-responses-data/get_visitors_400_bad_request.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "error": "bad request" -} diff --git a/tests/mocked-responses-tests/mocked-responses-data/get_visitors_403_forbidden.json b/tests/mocked-responses-tests/mocked-responses-data/get_visitors_403_forbidden.json deleted file mode 100644 index 8a886d18..00000000 --- a/tests/mocked-responses-tests/mocked-responses-data/get_visitors_403_forbidden.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "error": "Forbidden (HTTP 403)" -} diff --git a/tests/mocked-responses-tests/mocked-responses-data/get_visitors_429_too_many_requests.json b/tests/mocked-responses-tests/mocked-responses-data/get_visitors_429_too_many_requests.json deleted file mode 100644 index 00d00f2e..00000000 --- a/tests/mocked-responses-tests/mocked-responses-data/get_visitors_429_too_many_requests.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "error": "too many requests" -} diff --git a/tests/mocked-responses-tests/mocked-responses-data/related-visitors/get_related_visitors_200.json b/tests/mocked-responses-tests/mocked-responses-data/related-visitors/get_related_visitors_200.json deleted file mode 100644 index 7a46a69e..00000000 --- a/tests/mocked-responses-tests/mocked-responses-data/related-visitors/get_related_visitors_200.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "relatedVisitors": [ - { - "visitorId": "NtCUJGceWX9RpvSbhvOm" - }, - { - "visitorId": "25ee02iZwGxeyT0jMNkZ" - } - ] -} diff --git a/tests/mocked-responses-tests/mocked-responses-data/related-visitors/get_related_visitors_200_empty.json b/tests/mocked-responses-tests/mocked-responses-data/related-visitors/get_related_visitors_200_empty.json deleted file mode 100644 index 6c9b02c1..00000000 --- a/tests/mocked-responses-tests/mocked-responses-data/related-visitors/get_related_visitors_200_empty.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "relatedVisitors": [] -} diff --git a/tests/mocked-responses-tests/mocked-responses-data/update_event_multiple_fields_request.json b/tests/mocked-responses-tests/mocked-responses-data/update_event_multiple_fields_request.json deleted file mode 100644 index f85d2e75..00000000 --- a/tests/mocked-responses-tests/mocked-responses-data/update_event_multiple_fields_request.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "linkedId": "myNewLinkedId", - "tag": { - "myTag": "myNewValue" - }, - "suspect": true -} diff --git a/tests/mocked-responses-tests/mocked-responses-data/update_event_one_field_request.json b/tests/mocked-responses-tests/mocked-responses-data/update_event_one_field_request.json deleted file mode 100644 index 0ebd1549..00000000 --- a/tests/mocked-responses-tests/mocked-responses-data/update_event_one_field_request.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "linkedId": "myNewLinkedId" -} diff --git a/tests/mocked-responses-tests/mocked-responses-data/webhook.json b/tests/mocked-responses-tests/mocked-responses-data/webhook.json deleted file mode 100644 index 1180fcdc..00000000 --- a/tests/mocked-responses-tests/mocked-responses-data/webhook.json +++ /dev/null @@ -1,293 +0,0 @@ -{ - "requestId": "Px6VxbRC6WBkA39yeNH3", - "url": "https://banking.example.com/signup", - "ip": "216.3.128.12", - "tag": { - "requestType": "signup", - "yourCustomId": 45321 - }, - "time": "2019-10-12T07:20:50.52Z", - "timestamp": 1554910997788, - "ipLocation": { - "accuracyRadius": 1, - "city": { - "name": "Bolingbrook" - }, - "continent": { - "code": "NA", - "name": "North America" - }, - "country": { - "code": "US", - "name": "United States" - }, - "latitude": 41.12933, - "longitude": -88.9954, - "postalCode": "60547", - "subdivisions": [ - { - "isoCode": "IL", - "name": "Illinois" - } - ], - "timezone": "America/Chicago" - }, - "linkedId": "any-string", - "visitorId": "3HNey93AkBW6CRbxV6xP", - "visitorFound": true, - "confidence": { - "score": 0.97 - }, - "firstSeenAt": { - "global": "2022-03-16T11:26:45.362Z", - "subscription": "2022-03-16T11:31:01.101Z" - }, - "lastSeenAt": { - "global": "2022-03-16T11:28:34.023Z", - "subscription": null - }, - "browserDetails": { - "browserName": "Chrome", - "browserFullVersion": "73.0.3683.86", - "browserMajorVersion": "73", - "os": "Mac OS X", - "osVersion": "10.14.3", - "device": "Other", - "userAgent": "(Macintosh; Intel Mac OS X 10_14_3) Chrome/73.0.3683.86" - }, - "incognito": false, - "clientReferrer": "https://google.com?search=banking+services", - "bot": { - "result": "bad", - "type": "selenium" - }, - "userAgent": "(Macintosh; Intel Mac OS X 10_14_3) Chrome/73.0.3683.86", - "rootApps": { - "result": false - }, - "emulator": { - "result": false - }, - "ipInfo": { - "v4": { - "address": "94.142.239.124", - "geolocation": { - "accuracyRadius": 20, - "latitude": 50.05, - "longitude": 14.4, - "postalCode": "150 00", - "timezone": "Europe/Prague", - "city": { - "name": "Prague" - }, - "country": { - "code": "CZ", - "name": "Czechia" - }, - "continent": { - "code": "EU", - "name": "Europe" - }, - "subdivisions": [ - { - "isoCode": "10", - "name": "Hlavni mesto Praha" - } - ] - }, - "asn": { - "asn": "7922", - "name": "COMCAST-7922", - "network": "73.136.0.0/13" - }, - "datacenter": { - "result": true, - "name": "DediPath" - } - } - }, - "ipBlocklist": { - "result": false, - "details": { - "emailSpam": false, - "attackSource": false - } - }, - "tor": { - "result": false - }, - "vpn": { - "result": false, - "confidence": "high", - "originTimezone": "Europe/Berlin", - "originCountry": "unknown", - "methods": { - "timezoneMismatch": false, - "publicVPN": false, - "auxiliaryMobile": false, - "osMismatch": false, - "relay": false - } - }, - "proxy": { - "result": true, - "confidence": "high", - "details": { - "proxyType": "residential", - "lastSeenAt": "2025-08-12T13:00:00Z" - } - }, - "tampering": { - "result": false, - "anomalyScore": 0, - "antiDetectBrowser": false - }, - "clonedApp": { - "result": false - }, - "factoryReset": { - "time": "1970-01-01T00:00:00Z", - "timestamp": 0 - }, - "jailbroken": { - "result": false - }, - "frida": { - "result": false - }, - "privacySettings": { - "result": false - }, - "virtualMachine": { - "result": false - }, - "rawDeviceAttributes": { - "architecture": { - "value": 127 - }, - "audio": { - "value": 35.73832903057337 - }, - "canvas": { - "value": { - "Winding": true, - "Geometry": "4dce9d6017c3e0c052a77252f29f2b1c", - "Text": "dd2474a56ff78c1de3e7a07070ba3b7d" - } - }, - "colorDepth": { - "value": 30 - }, - "colorGamut": { - "value": "srgb" - }, - "contrast": { - "value": 0 - }, - "cookiesEnabled": { - "value": true - } - }, - "highActivity": { - "result": false - }, - "locationSpoofing": { - "result": true - }, - "suspectScore": { - "result": 0 - }, - "velocity": { - "distinctIp": { - "intervals": { - "5m": 1, - "1h": 1, - "24h": 1 - } - }, - "distinctLinkedId": {}, - "distinctCountry": { - "intervals": { - "5m": 1, - "1h": 2, - "24h": 2 - } - }, - "events": { - "intervals": { - "5m": 1, - "1h": 5, - "24h": 5 - } - }, - "ipEvents": { - "intervals": { - "5m": 1, - "1h": 5, - "24h": 5 - } - }, - "distinctIpByLinkedId": { - "intervals": { - "5m": 1, - "1h": 5, - "24h": 5 - } - }, - "distinctVisitorIdByLinkedId": { - "intervals": { - "5m": 1, - "1h": 5, - "24h": 5 - } - } - }, - "developerTools": { - "result": false - }, - "mitmAttack": { - "result": false - }, - "sdk": { - "platform": "js", - "version": "3.11.10" - }, - "replayed": false, - "supplementaryIds": { - "standard": { - "visitorId": "3HNey93AkBW6CRbxV6xP", - "visitorFound": true, - "confidence": { - "score": 0.97 - }, - "firstSeenAt": { - "global": "2022-03-16T11:26:45.362Z", - "subscription": "2022-03-16T11:31:01.101Z" - }, - "lastSeenAt": { - "global": "2022-03-16T11:28:34.023Z", - "subscription": "2022-03-16T11:28:34.023Z" - } - }, - "highRecall": { - "visitorId": "3HNey93AkBW6CRbxV6xP", - "visitorFound": true, - "confidence": { - "score": 0.97 - }, - "firstSeenAt": { - "global": "2022-03-16T11:26:45.362Z", - "subscription": "2022-03-16T11:31:01.101Z" - }, - "lastSeenAt": { - "global": "2022-03-16T11:28:34.023Z", - "subscription": "2022-03-16T11:28:34.023Z" - } - } - }, - "proximity": { - "id": "w1aTfd4MCvl", - "precisionRadius": 10, - "confidence": 0.95 - } -} diff --git a/tests/mocked-responses-tests/mocked-responses-data/webhook/webhook_event.json b/tests/mocked-responses-tests/mocked-responses-data/webhook/webhook_event.json new file mode 100644 index 00000000..d9e597b2 --- /dev/null +++ b/tests/mocked-responses-tests/mocked-responses-data/webhook/webhook_event.json @@ -0,0 +1,282 @@ +{ + "linked_id": "somelinkedId", + "tags": {}, + "timestamp": 1708102555327, + "event_id": "1708102555327.NLOjmg", + "url": "https://www.example.com/login?hope{this{works[!", + "ip_address": "61.127.217.15", + "user_agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64) ....", + "client_referrer": "https://example.com/blog/my-article", + "browser_details": { + "browser_name": "Chrome", + "browser_major_version": "74", + "browser_full_version": "74.0.3729", + "os": "Windows", + "os_version": "7", + "device": "Other" + }, + "identification": { + "visitor_id": "Ibk1527CUFmcnjLwIs4A9", + "confidence": { + "score": 0.97, + "version": "1.1" + }, + "visitor_found": false, + "first_seen_at": 1708102555327, + "last_seen_at": 1708102555327 + }, + "supplementary_id_high_recall": { + "visitor_id": "3HNey93AkBW6CRbxV6xP", + "visitor_found": true, + "confidence": { + "score": 0.97, + "version": "1.1" + }, + "first_seen_at": 1708102555327, + "last_seen_at": 1708102555327 + }, + "proximity": { + "id": "w1aTfd4MCvl", + "precision_radius": 10, + "confidence": 0.95 + }, + "bot": "not_detected", + "root_apps": false, + "emulator": false, + "ip_info": { + "v4": { + "address": "94.142.239.124", + "geolocation": { + "accuracy_radius": 20, + "latitude": 50.05, + "longitude": 14.4, + "postal_code": "150 00", + "timezone": "Europe/Prague", + "city_name": "Prague", + "country_code": "CZ", + "country_name": "Czechia", + "continent_code": "EU", + "continent_name": "Europe", + "subdivisions": [ + { + "iso_code": "10", + "name": "Hlavni mesto Praha" + } + ] + }, + "asn": "7922", + "asn_name": "COMCAST-7922", + "asn_network": "73.136.0.0/13", + "asn_type": "isp", + "datacenter_result": true, + "datacenter_name": "DediPath" + }, + "v6": { + "address": "2001:db8:3333:4444:5555:6666:7777:8888", + "geolocation": { + "accuracy_radius": 5, + "latitude": 49.982, + "longitude": 36.2566, + "postal_code": "10112", + "timezone": "Europe/Berlin", + "city_name": "Berlin", + "country_code": "DE", + "country_name": "Germany", + "continent_code": "EU", + "continent_name": "Europe", + "subdivisions": [ + { + "iso_code": "BE", + "name": "Land Berlin" + } + ] + }, + "asn": "6805", + "asn_name": "Telefonica Germany", + "asn_network": "2a02:3100::/24", + "asn_type": "isp", + "datacenter_result": false, + "datacenter_name": "" + } + }, + "ip_blocklist": { + "email_spam": false, + "attack_source": false, + "tor_node": false + }, + "proxy": true, + "proxy_confidence": "low", + "proxy_details": { + "proxy_type": "residential", + "last_seen_at": 1708102555327, + "provider": "Massive" + }, + "vpn": false, + "vpn_confidence": "high", + "vpn_origin_timezone": "Europe/Berlin", + "vpn_origin_country": "unknown", + "vpn_methods": { + "timezone_mismatch": false, + "public_vpn": false, + "auxiliary_mobile": false, + "os_mismatch": false, + "relay": false + }, + "incognito": false, + "tampering": false, + "tampering_details": { + "anomaly_score": 0.1955, + "anti_detect_browser": false + }, + "cloned_app": false, + "factory_reset_timestamp": 0, + "jailbroken": false, + "frida": false, + "privacy_settings": false, + "virtual_machine": false, + "location_spoofing": false, + "velocity": { + "distinct_ip": { + "5_minutes": 1, + "1_hour": 1, + "24_hours": 1 + }, + "distinct_country": { + "5_minutes": 1, + "1_hour": 2, + "24_hours": 2 + }, + "events": { + "5_minutes": 1, + "1_hour": 5, + "24_hours": 5 + }, + "ip_events": { + "5_minutes": 1, + "1_hour": 5, + "24_hours": 5 + }, + "distinct_ip_by_linked_id": { + "5_minutes": 1, + "1_hour": 5, + "24_hours": 5 + }, + "distinct_visitor_id_by_linked_id": { + "5_minutes": 1, + "1_hour": 5, + "24_hours": 5 + } + }, + "developer_tools": false, + "mitm_attack": false, + "sdk": { + "platform": "js", + "version": "3.11.10", + "integrations": [ + { + "name": "fingerprint-pro-react", + "version": "3.11.10", + "subintegration": { + "name": "preact", + "version": "10.21.0" + } + } + ] + }, + "replayed": false, + "high_activity_device": false, + "raw_device_attributes": { + "math": "5f030fa7d2e5f9f757bfaf81642eb1a6", + "vendor": "Google Inc.", + "plugins": [ + { + "description": "Portable Document Format", + "mimeTypes": [ + { + "suffixes": "pdf", + "type": "application/pdf" + }, + { + "suffixes": "pdf", + "type": "text/pdf" + } + ], + "name": "PDF Viewer" + } + ], + "webgl_extensions": { + "context_attributes": "6b1ed336830d2bc96442a9d76373252a", + "extension_parameters": "86a8abb36f0cb30b5946dec0c761d042", + "extensions": "57233d7b10f89fcd1ff95e3837ccd72d", + "parameters": "ea118c48e308bc4b0677118bbb3019ec", + "shader_precisions": "f223dfbcd580cf142da156d93790eb83", + "unsupported_extensions": [] + }, + "cookies_enabled": true, + "webgl_basics": { + "renderer": "WebKit WebGL", + "renderer_unmasked": "ANGLE (Apple, ANGLE Metal Renderer: Apple M4, Unspecified Version)", + "shading_language_version": "WebGL GLSL ES 1.0 (OpenGL ES GLSL ES 1.0 Chromium)", + "vendor": "WebKit", + "vendor_unmasked": "Google Inc. (Apple)", + "version": "WebGL 1.0 (OpenGL ES 2.0 Chromium)" + }, + "canvas": { + "geometry": "db3c1462576a399a03ae93d0ab9eb5c4", + "text": "70c3d3f7eb4408dc37a6bf8af1c51029", + "winding": true + }, + "hardware_concurrency": 10, + "languages": [ + [ + "en-US" + ] + ], + "color_depth": 24, + "fonts": [ + "Arial Unicode MS", + "Gill Sans", + "Helvetica Neue", + "Menlo" + ], + "indexed_db": true, + "touch_support": { + "max_touch_points": 0, + "touch_event": false, + "touch_start": false + }, + "device_memory": 8, + "oscpu": "Windows NT 6.1; Win64; x64", + "architecture": 127, + "screen_resolution": [ + 1920, + 1080 + ], + "timezone": "America/Sao_Paulo", + "emoji": { + "bottom": 32, + "font": "Times", + "height": 18, + "left": 8, + "right": 1608, + "top": 14, + "width": 1600, + "x": 8, + "y": 14 + }, + "font_preferences": { + "apple": 147.5625, + "default": 147.5625, + "min": 9.234375, + "mono": 133.0625, + "sans": 144.015625, + "serif": 147.5625, + "system": 146.09375 + }, + "platform": "MacIntel", + "local_storage": true, + "session_storage": true, + "date_time_locale": "en-US", + "audio": 124.04347745512496 + } +} \ No newline at end of file diff --git a/tests/mocked-responses-tests/searchEventsTests.spec.ts b/tests/mocked-responses-tests/searchEventsTests.spec.ts index 3667a0f4..ef96be86 100644 --- a/tests/mocked-responses-tests/searchEventsTests.spec.ts +++ b/tests/mocked-responses-tests/searchEventsTests.spec.ts @@ -1,5 +1,7 @@ -import { ErrorResponse, FingerprintJsServerApiClient, getIntegrationInfo, RequestError } from '../../src' -import getEventsSearch from './mocked-responses-data/get_event_search_200.json' +import { ErrorResponse, FingerprintServerApiClient, RequestError, SearchEventsFilter } from '../../src' +import getEventsSearch from './mocked-responses-data/events/search/get_event_search_200.json' +import { createJsonResponse } from './utils' +import { getIntegrationInfo } from '../../src/urlUtils' jest.spyOn(global, 'fetch') @@ -7,64 +9,72 @@ const mockFetch = fetch as unknown as jest.Mock describe('[Mocked response] Search Events', () => { const apiKey = 'dummy_api_key' - const client = new FingerprintJsServerApiClient({ apiKey }) + const client = new FingerprintServerApiClient({ apiKey }) test('without filter', async () => { - mockFetch.mockReturnValue(Promise.resolve(new Response(JSON.stringify(getEventsSearch)))) + mockFetch.mockReturnValue(Promise.resolve(createJsonResponse(getEventsSearch))) + + const limit = 10 const response = await client.searchEvents({ - limit: 10, + limit, }) expect(response).toEqual(getEventsSearch) expect(mockFetch).toHaveBeenCalledWith( - `https://api.fpjs.io/events/search?limit=10&ii=${encodeURIComponent(getIntegrationInfo())}`, + `https://api.fpjs.io/v4/events?limit=${limit}&ii=${encodeURIComponent(getIntegrationInfo())}`, { - headers: { 'Auth-API-Key': apiKey }, + headers: { Authorization: `Bearer ${apiKey}` }, method: 'GET', } ) }) test('with filter params passed as undefined', async () => { - mockFetch.mockReturnValue(Promise.resolve(new Response(JSON.stringify(getEventsSearch)))) + mockFetch.mockReturnValue(Promise.resolve(createJsonResponse(getEventsSearch))) + + const limit = 10 const response = await client.searchEvents({ - limit: 10, + limit, ip_address: undefined, visitor_id: undefined, }) expect(response).toEqual(getEventsSearch) expect(mockFetch).toHaveBeenCalledWith( - `https://api.fpjs.io/events/search?limit=10&ii=${encodeURIComponent(getIntegrationInfo())}`, + `https://api.fpjs.io/v4/events?limit=${limit}&ii=${encodeURIComponent(getIntegrationInfo())}`, { - headers: { 'Auth-API-Key': apiKey }, + headers: { Authorization: `Bearer ${apiKey}` }, method: 'GET', } ) }) test('with partial filter', async () => { - mockFetch.mockReturnValue(Promise.resolve(new Response(JSON.stringify(getEventsSearch)))) + mockFetch.mockReturnValue(Promise.resolve(createJsonResponse(getEventsSearch))) + + const limit = 10 + const bot = 'good' + const visitorId = 'visitor_id' const response = await client.searchEvents({ - limit: 10, - bot: 'good', - visitor_id: 'visitor_id', + limit, + bot, + visitor_id: visitorId, }) expect(response).toEqual(getEventsSearch) expect(mockFetch).toHaveBeenCalledWith( - `https://api.fpjs.io/events/search?limit=10&bot=good&visitor_id=visitor_id&ii=${encodeURIComponent(getIntegrationInfo())}`, + `https://api.fpjs.io/v4/events?limit=${limit}&bot=${bot}&visitor_id=${visitorId}&ii=${encodeURIComponent(getIntegrationInfo())}`, { - headers: { 'Auth-API-Key': apiKey }, + headers: { Authorization: `Bearer ${apiKey}` }, method: 'GET', } ) }) test('with all possible filters', async () => { - mockFetch.mockReturnValue(Promise.resolve(new Response(JSON.stringify(getEventsSearch)))) + mockFetch.mockReturnValue(Promise.resolve(createJsonResponse(getEventsSearch))) - const response = await client.searchEvents({ + const filters: SearchEventsFilter = { limit: 10, bot: 'all', visitor_id: 'visitor_id', @@ -88,44 +98,52 @@ describe('[Mocked response] Search Events', () => { vpn_confidence: 'medium', emulator: true, incognito: true, - ip_blocklist: true, - datacenter: true, developer_tools: true, location_spoofing: true, mitm_attack: true, proxy: true, sdk_version: 'testSdkVersion', sdk_platform: 'js', - environment: ['env1', 'env2', ''], // Cannot add null or undefined here because environment expects string or string array - proximity_id: 'testProximityId', - proximity_precision_radius: 10, - }) + environment: ['env1', 'env2', ''], + } + + const response = await client.searchEvents(filters) expect(response).toEqual(getEventsSearch) - expect(mockFetch).toHaveBeenCalledWith( - `https://api.fpjs.io/events/search?limit=10&bot=all&visitor_id=visitor_id&ip_address=${encodeURIComponent( - '192.168.0.1/32' - )}&linked_id=linked_id&start=1620000000000&end=1630000000000&reverse=true&suspect=false&anti_detect_browser=true&cloned_app=true&factory_reset=true&frida=true&jailbroken=true&min_suspect_score=0.5&privacy_settings=true&root_apps=true&tampering=true&virtual_machine=true&vpn=true&vpn_confidence=medium&emulator=true&incognito=true&ip_blocklist=true&datacenter=true&developer_tools=true&location_spoofing=true&mitm_attack=true&proxy=true&sdk_version=testSdkVersion&sdk_platform=js&environment%5B%5D=env1&environment%5B%5D=env2&environment%5B%5D=&proximity_id=testProximityId&proximity_precision_radius=10&ii=${encodeURIComponent( - getIntegrationInfo() - )}`, - { - headers: { 'Auth-API-Key': apiKey }, - method: 'GET', + const baseUrl = 'https://api.fpjs.io/v4/events' + const queryParams = new URLSearchParams() + for (const [key, value] of Object.entries(filters)) { + if (value === undefined || value === null) { + continue } - ) + + if (Array.isArray(value)) { + for (const v of value) { + queryParams.append(key, String(v)) + } + } else { + queryParams.set(key, String(value)) + } + } + queryParams.set('ii', getIntegrationInfo()) + + const expectedUrl = `${baseUrl}?${queryParams.toString()}` + + expect(mockFetch).toHaveBeenCalledWith(expectedUrl, { + headers: { Authorization: `Bearer ${apiKey}` }, + method: 'GET', + }) }) test('400 error', async () => { const error = { error: { + code: 'request_cannot_be_parsed', message: 'Forbidden', - code: 'RequestCannotBeParsed', }, } satisfies ErrorResponse - const mockResponse = new Response(JSON.stringify(error), { - status: 400, - }) + const mockResponse = createJsonResponse(error, 400) mockFetch.mockReturnValue(Promise.resolve(mockResponse)) await expect( client.searchEvents({ @@ -137,13 +155,11 @@ describe('[Mocked response] Search Events', () => { test('403 error', async () => { const error = { error: { + code: 'secret_api_key_required', message: 'secret key is required', - code: 'TokenRequired', }, } satisfies ErrorResponse - const mockResponse = new Response(JSON.stringify(error), { - status: 403, - }) + const mockResponse = createJsonResponse(error, 403) mockFetch.mockReturnValue(Promise.resolve(mockResponse)) await expect( client.searchEvents({ diff --git a/tests/mocked-responses-tests/updateEventTests.spec.ts b/tests/mocked-responses-tests/updateEventTests.spec.ts index 5809154d..ee6e83ac 100644 --- a/tests/mocked-responses-tests/updateEventTests.spec.ts +++ b/tests/mocked-responses-tests/updateEventTests.spec.ts @@ -1,9 +1,9 @@ -import { ErrorResponse, FingerprintJsServerApiClient, getIntegrationInfo, Region, RequestError } from '../../src' -import Error404 from './mocked-responses-data/errors/404_request_not_found.json' +import { ErrorResponse, FingerprintServerApiClient, Region, RequestError, SdkError } from '../../src' +import Error404 from './mocked-responses-data/errors/404_event_not_found.json' import Error403 from './mocked-responses-data/errors/403_feature_not_enabled.json' import Error400 from './mocked-responses-data/errors/400_request_body_invalid.json' import Error409 from './mocked-responses-data/errors/409_state_not_ready.json' -import { SdkError } from '../../src/errors/apiErrors' +import { getIntegrationInfo } from '../../src/urlUtils' jest.spyOn(global, 'fetch') @@ -12,18 +12,18 @@ const mockFetch = fetch as unknown as jest.Mock describe('[Mocked response] Update event', () => { const apiKey = 'dummy_api_key' - const existingVisitorId = 'TaDnMBz9XCpZNuSzFUqP' + const existingEventId = 'TaDnMBz9XCpZNuSzFUqP' - const client = new FingerprintJsServerApiClient({ region: Region.EU, apiKey }) + const client = new FingerprintServerApiClient({ region: Region.EU, apiKey }) - test('with visitorId', async () => { - mockFetch.mockReturnValue(Promise.resolve(new Response())) + test('with eventId', async () => { + mockFetch.mockReturnValue(Promise.resolve(new Response(undefined, { headers: { 'content-length': '0' } }))) const body = { - linkedId: 'linked_id', + linked_id: 'linked_id', suspect: true, } - const response = await client.updateEvent(body, existingVisitorId) + const response = await client.updateEvent(body, existingEventId) expect(response).toBeUndefined() @@ -32,10 +32,10 @@ describe('[Mocked response] Update event', () => { expect(JSON.parse(bodyFromCall)).toEqual(body) expect(mockFetch).toHaveBeenCalledWith( - `https://eu.api.fpjs.io/events/${existingVisitorId}?ii=${encodeURIComponent(getIntegrationInfo())}`, + `https://eu.api.fpjs.io/v4/events/${existingEventId}?ii=${encodeURIComponent(getIntegrationInfo())}`, { - headers: { 'Auth-API-Key': 'dummy_api_key' }, - method: 'PUT', + headers: { Authorization: `Bearer ${apiKey}` }, + method: 'PATCH', body: JSON.stringify(body), } ) @@ -44,14 +44,15 @@ describe('[Mocked response] Update event', () => { test('404 error', async () => { const mockResponse = new Response(JSON.stringify(Error404), { status: 404, + headers: { 'content-type': 'application/json' }, }) mockFetch.mockReturnValue(Promise.resolve(mockResponse)) const body = { - linkedId: 'linked_id', + linked_id: 'linked_id', suspect: true, } - await expect(client.updateEvent(body, existingVisitorId)).rejects.toThrow( + await expect(client.updateEvent(body, existingEventId)).rejects.toThrow( RequestError.fromErrorResponse(Error404 as ErrorResponse, mockResponse) ) }) @@ -59,14 +60,15 @@ describe('[Mocked response] Update event', () => { test('403 error', async () => { const mockResponse = new Response(JSON.stringify(Error403), { status: 403, + headers: { 'content-type': 'application/json' }, }) mockFetch.mockReturnValue(Promise.resolve(mockResponse)) const body = { - linkedId: 'linked_id', + linked_id: 'linked_id', suspect: true, } - await expect(client.updateEvent(body, existingVisitorId)).rejects.toThrow( + await expect(client.updateEvent(body, existingEventId)).rejects.toThrow( RequestError.fromErrorResponse(Error403 as ErrorResponse, mockResponse) ) }) @@ -74,14 +76,15 @@ describe('[Mocked response] Update event', () => { test('400 error', async () => { const mockResponse = new Response(JSON.stringify(Error400), { status: 400, + headers: { 'content-type': 'application/json' }, }) mockFetch.mockReturnValue(Promise.resolve(mockResponse)) const body = { - linkedId: 'linked_id', + linked_id: 'linked_id', suspect: true, } - await expect(client.updateEvent(body, existingVisitorId)).rejects.toThrow( + await expect(client.updateEvent(body, existingEventId)).rejects.toThrow( RequestError.fromErrorResponse(Error400 as ErrorResponse, mockResponse) ) }) @@ -89,14 +92,15 @@ describe('[Mocked response] Update event', () => { test('409 error', async () => { const mockResponse = new Response(JSON.stringify(Error409), { status: 409, + headers: { 'content-type': 'application/json' }, }) mockFetch.mockReturnValue(Promise.resolve(mockResponse)) const body = { - linkedId: 'linked_id', + linked_id: 'linked_id', suspect: true, } - await expect(client.updateEvent(body, existingVisitorId)).rejects.toThrow( + await expect(client.updateEvent(body, existingEventId)).rejects.toThrow( RequestError.fromErrorResponse(Error409 as ErrorResponse, mockResponse) ) }) @@ -108,10 +112,10 @@ describe('[Mocked response] Update event', () => { mockFetch.mockReturnValue(Promise.resolve(mockResponse)) const body = { - linkedId: 'linked_id', + linked_id: 'linked_id', suspect: true, } - await expect(client.updateEvent(body, existingVisitorId)).rejects.toMatchObject( + await expect(client.updateEvent(body, existingEventId)).rejects.toMatchObject( new SdkError( 'Failed to parse JSON response', mockResponse, @@ -121,23 +125,23 @@ describe('[Mocked response] Update event', () => { }) test('Error with bad shape', async () => { - const errorInfo = 'Some text instead of shaped object' const mockResponse = new Response( JSON.stringify({ - error: errorInfo, + error: 'Unexpected error format', }), { status: 404, + headers: { 'content-type': 'application/json' }, } ) mockFetch.mockReturnValue(Promise.resolve(mockResponse)) const body = { - linkedId: 'linked_id', + linked_id: 'linked_id', suspect: true, } - await expect(client.updateEvent(body, existingVisitorId)).rejects.toThrow(RequestError) - await expect(client.updateEvent(body, existingVisitorId)).rejects.toThrow('Some text instead of shaped object') + await expect(client.updateEvent(body, existingEventId)).rejects.toThrow(RequestError) + await expect(client.updateEvent(body, existingEventId)).rejects.toThrow('Unknown error') }) }) diff --git a/tests/mocked-responses-tests/utils.ts b/tests/mocked-responses-tests/utils.ts new file mode 100644 index 00000000..4b4d40ea --- /dev/null +++ b/tests/mocked-responses-tests/utils.ts @@ -0,0 +1,7 @@ +export const createJsonResponse = (resp: object, status: number = 200) => + new Response(JSON.stringify(resp), { + headers: { + 'content-type': 'application/json', + }, + status, + }) diff --git a/tests/unit-tests/__snapshots__/sealedResults.spec.ts.snap b/tests/unit-tests/__snapshots__/sealedResults.spec.ts.snap index 01e8165b..62912390 100644 --- a/tests/unit-tests/__snapshots__/sealedResults.spec.ts.snap +++ b/tests/unit-tests/__snapshots__/sealedResults.spec.ts.snap @@ -2,81 +2,171 @@ exports[`Unseal event response unseals sealed data using aes256gcm 1`] = ` { - "products": { - "botd": { - "data": { - "bot": { - "result": "notDetected", - }, - "ip": "::1", - "meta": { - "foo": "bar", - }, - "requestId": "1703067132750.Z5hutJ", - "time": "2023-12-20T10:12:13.894Z", - "url": "http://localhost:8080/", - "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.3 Safari/605.1.15", - }, + "bot": "not_detected", + "browser_details": { + "browser_full_version": "74.0.3729", + "browser_major_version": "74", + "browser_name": "Chrome", + "device": "Other", + "os": "Windows", + "os_version": "7", + }, + "cloned_app": false, + "developer_tools": false, + "emulator": false, + "event_id": "1708102555327.NLOjmg", + "factory_reset_timestamp": 0, + "frida": false, + "identification": { + "confidence": { + "score": 0.97, + "version": "1.1", }, - "identification": { - "data": { - "browserDetails": { - "browserFullVersion": "17.3", - "browserMajorVersion": "17", - "browserName": "Safari", - "device": "Other", - "os": "Mac OS X", - "osVersion": "10.15.7", - "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.3 Safari/605.1.15", - }, - "confidence": { - "score": 1, - }, - "firstSeenAt": { - "global": "2023-12-15T12:13:55.103Z", - "subscription": "2023-12-15T12:13:55.103Z", - }, - "incognito": false, - "ip": "::1", - "ipLocation": { - "accuracyRadius": 1000, - "city": { - "name": "Stockholm", - }, - "continent": { - "code": "EU", - "name": "Europe", + "first_seen_at": 1708102555327, + "last_seen_at": 1708102555327, + "visitor_found": false, + "visitor_id": "Ibk1527CUFmcnjLwIs4A9", + }, + "incognito": false, + "ip_address": "61.127.217.15", + "ip_blocklist": { + "attack_source": false, + "email_spam": false, + "tor_node": false, + }, + "ip_info": { + "v4": { + "address": "94.142.239.124", + "asn": "7922", + "asn_name": "COMCAST-7922", + "asn_network": "73.136.0.0/13", + "datacenter_name": "DediPath", + "datacenter_result": true, + "geolocation": { + "accuracy_radius": 20, + "city_name": "Prague", + "continent_code": "EU", + "continent_name": "Europe", + "country_code": "CZ", + "country_name": "Czechia", + "latitude": 50.05, + "longitude": 14.4, + "postal_code": "150 00", + "subdivisions": [ + { + "iso_code": "10", + "name": "Hlavni mesto Praha", }, - "country": { - "code": "SE", - "name": "Sweden", + ], + "timezone": "Europe/Prague", + }, + }, + "v6": { + "address": "2001:db8:3333:4444:5555:6666:7777:8888", + "asn": "6805", + "asn_name": "Telefonica Germany", + "asn_network": "2a02:3100::/24", + "datacenter_name": "", + "datacenter_result": false, + "geolocation": { + "accuracy_radius": 5, + "city_name": "Berlin", + "continent_code": "EU", + "continent_name": "Europe", + "country_code": "DE", + "country_name": "Germany", + "latitude": 49.982, + "longitude": 36.2566, + "postal_code": "10112", + "subdivisions": [ + { + "iso_code": "BE", + "name": "Land Berlin", }, - "latitude": 59.3241, - "longitude": 18.0517, - "postalCode": "100 05", - "subdivisions": [ - { - "isoCode": "AB", - "name": "Stockholm County", - }, - ], - "timezone": "Europe/Stockholm", - }, - "lastSeenAt": { - "global": "2023-12-19T11:39:51.52Z", - "subscription": "2023-12-19T11:39:51.52Z", - }, - "requestId": "1703067132750.Z5hutJ", - "tag": { - "foo": "bar", - }, - "time": "2023-12-20T10:12:16Z", - "timestamp": 1703067136286, - "url": "http://localhost:8080/", - "visitorFound": true, - "visitorId": "2ZEDCZEfOfXjEmMuE3tq", + ], + "timezone": "Europe/Berlin", }, }, }, + "jailbroken": false, + "linked_id": "somelinkedId", + "location_spoofing": false, + "mitm_attack": false, + "privacy_settings": false, + "proxy": true, + "proxy_confidence": "low", + "proxy_details": { + "last_seen_at": 1708102555327, + "proxy_type": "residential", + }, + "replayed": false, + "root_apps": false, + "sdk": { + "platform": "js", + "version": "3.11.10", + }, + "supplementary_id_high_recall": { + "confidence": { + "score": 0.97, + "version": "1.1", + }, + "first_seen_at": 1708102555327, + "last_seen_at": 1708102555327, + "visitor_found": true, + "visitor_id": "3HNey93AkBW6CRbxV6xP", + }, + "tags": {}, + "tampering": false, + "tampering_details": { + "anomaly_score": 0.1955, + "anti_detect_browser": false, + }, + "timestamp": 1708102555327, + "url": "https://www.example.com/login?hope{this{works[!", + "user_agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64) ....", + "velocity": { + "distinct_country": { + "1_hour": 2, + "24_hours": 2, + "5_minutes": 1, + }, + "distinct_ip": { + "1_hour": 1, + "24_hours": 1, + "5_minutes": 1, + }, + "distinct_ip_by_linked_id": { + "1_hour": 5, + "24_hours": 5, + "5_minutes": 1, + }, + "distinct_visitor_id_by_linked_id": { + "1_hour": 5, + "24_hours": 5, + "5_minutes": 1, + }, + "events": { + "1_hour": 5, + "24_hours": 5, + "5_minutes": 1, + }, + "ip_events": { + "1_hour": 5, + "24_hours": 5, + "5_minutes": 1, + }, + }, + "virtual_machine": false, + "vpn": false, + "vpn_confidence": "high", + "vpn_methods": { + "auxiliary_mobile": false, + "os_mismatch": false, + "public_vpn": false, + "relay": false, + "timezone_mismatch": false, + }, + "vpn_origin_country": "unknown", + "vpn_origin_timezone": "Europe/Berlin", } `; diff --git a/tests/unit-tests/sealedResults.spec.ts b/tests/unit-tests/sealedResults.spec.ts index 620885c9..9eb5557e 100644 --- a/tests/unit-tests/sealedResults.spec.ts +++ b/tests/unit-tests/sealedResults.spec.ts @@ -16,7 +16,7 @@ describe('Parse events response', () => { describe('Unseal event response', () => { const sealedData = Buffer.from( - 'noXc7SXO+mqeAGrvBMgObi/S0fXTpP3zupk8qFqsO/1zdtWCD169iLA3VkkZh9ICHpZ0oWRzqG0M9/TnCeKFohgBLqDp6O0zEfXOv6i5q++aucItznQdLwrKLP+O0blfb4dWVI8/aSbd4ELAZuJJxj9bCoVZ1vk+ShbUXCRZTD30OIEAr3eiG9aw00y1UZIqMgX6CkFlU9L9OnKLsNsyomPIaRHTmgVTI5kNhrnVNyNsnzt9rY7fUD52DQxJILVPrUJ1Q+qW7VyNslzGYBPG0DyYlKbRAomKJDQIkdj/Uwa6bhSTq4XYNVvbk5AJ/dGwvsVdOnkMT2Ipd67KwbKfw5bqQj/cw6bj8Cp2FD4Dy4Ud4daBpPRsCyxBM2jOjVz1B/lAyrOp8BweXOXYugwdPyEn38MBZ5oL4D38jIwR/QiVnMHpERh93jtgwh9Abza6i4/zZaDAbPhtZLXSM5ztdctv8bAb63CppLU541Kf4OaLO3QLvfLRXK2n8bwEwzVAqQ22dyzt6/vPiRbZ5akh8JB6QFXG0QJF9DejsIspKF3JvOKjG2edmC9o+GfL3hwDBiihYXCGY9lElZICAdt+7rZm5UxMx7STrVKy81xcvfaIp1BwGh/HyMsJnkE8IczzRFpLlHGYuNDxdLoBjiifrmHvOCUDcV8UvhSV+UAZtAVejdNGo5G/bz0NF21HUO4pVRPu6RqZIs/aX4hlm6iO/0Ru00ct8pfadUIgRcephTuFC2fHyZxNBC6NApRtLSNLfzYTTo/uSjgcu6rLWiNo5G7yfrM45RXjalFEFzk75Z/fu9lCJJa5uLFgDNKlU+IaFjArfXJCll3apbZp4/LNKiU35ZlB7ZmjDTrji1wLep8iRVVEGht/DW00MTok7Zn7Fv+MlxgWmbZB3BuezwTmXb/fNw==', + 'noXc7Xu7PIKu1tbMkMxLbQG4XU46Bv5dED98hqTkPYZnmb8PG81Q83Kpg541Vt4NQdkzfezDSVk8FP9ZzJ08L0MMb4S8bT78c10Op1LyKwZU6DGr1e3V+ZWcNzHVG1rPoL+eUHN6yR9MQp8/CmSUBQUPOOAUXdoqWohbfIGxoQIuQ5BtfpSJuYD6kTyswSi56wxzY/s24dMwgS2KnA81Y1pdi3ZVJKBdwGYGg4T5Dvcqu0GWv3sScKD9b4Tagfbe2m8nbXY/QtN770c7J1xo/TNXXdq4lyqaMyqIayHOwRBP58tNF8mACusm1pogOVIt456wIMetCGKxicPJr7m/Q02ONzhkMtzzXwgwriglGHfM7UbtTsCytCBP7J2vp0tEkHiq/X3qtuvSLJqNyRzwFJhgisKGftc5CIaT2VxVKKxkL/6Ws6FPm4sQB1UGtMCMftKpyb1lFzG9lwFkKvYN9+FGtvRM50mbrzz7ONDxbwykkxihAab36MIuk7dfhvnVLFAjrpuCkEFdWrtjVyWmM0xVeXpEUtP6Ijk5P+VuPZ1alV/JV1q4WvfrGMizEZbwbp6eQZg9mwKe4IX+FVi7sPF2S/CCLI/d90S5Yz6bBP9uiQ3pCVlYbVOkpwS0YQxnR+h5J50qodY7LuswNO5VlEgI0ztkjPQBr8koT4SM54X2z14tA2tKCxSv1psEL5HOk4IWN+9f3RVfDKBDruDiDd+BtZquhYLmOFat9K4h41NrPGAqv5tKmmJtx3llMs6LFHPKBlNlI5zgqE7T47xv2AWw5nqWM107t8lpRETIgJx+YN/Jv6byJSQm7afaeDtHXGceMPOKMziH1XgsiQiS56OsmyyRgaq5YCmMuaPw8gcgVa7RNZSafkP34aQBAuJOA3JFs5xcYcubKutD3h1mk697A8vwdtR/Gj0zTvuUnQ/9o3qHSLseAEIiY9/dS6WJnKXRKTonQi2F6DV9NTzFVQl99AH22jq6lIsjbEEKcq/ydFDUpgAq4lyp9nPBHuPXSojdG+1BWuUyjYykaqnLzzqKgRalGzeWmRHd2qeNw8Bz5OWYBw82C3gHRS2BB9VquIgEYktDvgJ5yRfDYkp8qgxHoYeR88ijccWgdvk+WH78OPdwqA7rqdAYcWqn9KNozoxuYddc0fnrHbgaWpanCmPp0gNEeb4r+i9FDGPSkgYBdyrEPHblsDN/Ad1dhLIHEDEtQyv13s6tDRgLVvhowrzqIM+5cm/abyTDhXzSYDfCw2Wf90cBOMsbQBB2N2YRqnrpA50PGp+0IwlPL7qZj1N4JGhvQD0ux8Ood6AiXpdguj7DMP+T0laHIjWee5/xGZB6g3EsCdOZJjVj7hSE/L3eV4No0WcLqJ5DPOgw+FnvQpxndCTc8DW83tNm624lm7scu0A499vEFj1dhtq5gUxsGcqzm09+Vk2V/d0sa77Xocqe3bcfS5lXc/pHrOc1qKlK8kTr2AYNwjeJJ14euuin361WBETd1I6n8eIs02HyBas09o9lT7Nq05jsnbxej6d0q6GH7IYusiBFTJaAZ6UXOV5i1NOcw9jaGyHms3M2N/b2cmXFYTIFZSjSfbqoI6YZF73sMPhEZqfZ5Jjq+ZLMC3A+yFPFJOW/0oolUGbcC8TBVmLi37Z9Wgc338w2Jf+I94SdViku', 'base64' ) const validKey = Buffer.from('p2PA7MGy5tx56cnyJaFZMr96BCFwZeHjZV2EqMvTq53=', 'base64') diff --git a/tests/unit-tests/serverApiClientTests.spec.ts b/tests/unit-tests/serverApiClientTests.spec.ts index 15c3389c..f8f440a2 100644 --- a/tests/unit-tests/serverApiClientTests.spec.ts +++ b/tests/unit-tests/serverApiClientTests.spec.ts @@ -1,16 +1,24 @@ -import { RequestError, FingerprintJsServerApiClient, Region, AuthenticationMode } from '../../src' +import { RequestError, FingerprintServerApiClient, Region, Options, EventUpdate, SdkError } from '../../src' describe('ServerApiClient', () => { + it('should throw error if no token provided', async () => { + expect(() => { + new FingerprintServerApiClient({} as Readonly) + }).toThrow('Api key is not set') + }) + it('should support passing custom fetch implementation', async () => { - const mockFetch = jest.fn().mockResolvedValue(new Response(JSON.stringify({}))) + const mockFetch = jest + .fn() + .mockResolvedValue(new Response(JSON.stringify({}), { headers: { 'content-type': 'application/json' } })) - const client = new FingerprintJsServerApiClient({ + const client = new FingerprintServerApiClient({ fetch: mockFetch, apiKey: 'test', region: Region.Global, }) - await client.getVisits('visitorId') + await client.getEvent('eventId') expect(mockFetch).toHaveBeenCalledTimes(1) }) @@ -22,9 +30,14 @@ describe('ServerApiClient', () => { message: 'feature not enabled', }, } - const mockFetch = jest.fn().mockResolvedValue(new Response(JSON.stringify(responseBody), { status: 403 })) - const client = new FingerprintJsServerApiClient({ + const mockFetch = jest + .fn() + .mockResolvedValue( + new Response(JSON.stringify(responseBody), { status: 403, headers: { 'content-type': 'application/json' } }) + ) + + const client = new FingerprintServerApiClient({ fetch: mockFetch, apiKey: 'test', region: Region.Global, @@ -47,7 +60,7 @@ describe('ServerApiClient', () => { }) it('should support using a string constant for the Region', () => { - const client = new FingerprintJsServerApiClient({ + const client = new FingerprintServerApiClient({ apiKey: 'test', region: 'Global', }) @@ -57,46 +70,122 @@ describe('ServerApiClient', () => { expect(client).toBeTruthy() }) - it('should support using a string constant for AuthenticationMode.AuthHeader', async () => { - const mockFetch = jest.fn().mockResolvedValue(new Response(JSON.stringify({}))) + it('should throw error when using getEvent if eventId is empty', async () => { + const client = new FingerprintServerApiClient({ + apiKey: 'test', + region: 'Global', + }) - const client = new FingerprintJsServerApiClient({ - fetch: mockFetch, + await expect(client.getEvent(undefined as unknown as string)).rejects.toThrow(new TypeError('eventId is not set')) + }) + + it('should throw error when using updateEvent if body or eventId is empty', async () => { + const client = new FingerprintServerApiClient({ + apiKey: 'test', + region: 'Global', + }) + + await expect(client.updateEvent(undefined as unknown as EventUpdate, '')).rejects.toThrow( + new TypeError('body is not set') + ) + + await expect(client.updateEvent({ linked_id: '' }, undefined as unknown as string)).rejects.toThrow( + new TypeError('eventId is not set') + ) + }) + + it('should throw error when using deleteVisitorData if visitorId is empty', async () => { + const client = new FingerprintServerApiClient({ apiKey: 'test', + region: 'Global', + }) + + await expect(client.deleteVisitorData(undefined as unknown as string)).rejects.toThrow( + new TypeError('visitorId is not set') + ) + }) + + it('should support using a string constant for Authorization header', async () => { + const mockFetch = jest + .fn() + .mockResolvedValue(new Response(JSON.stringify({}), { headers: { 'content-type': 'application/json' } })) + + const apiKey = 'test' + + const client = new FingerprintServerApiClient({ + fetch: mockFetch, + apiKey, region: Region.Global, - authenticationMode: 'AuthHeader', }) - await client.getVisits('visitorId') + await client.getEvent('eventId') expect(mockFetch).toHaveBeenCalledTimes(1) - expect(mockFetch).toHaveBeenCalledWith(expect.not.stringContaining('api_key=test'), { + expect(mockFetch).toHaveBeenCalledWith(expect.anything(), { method: 'GET', headers: { - 'Auth-API-Key': 'test', + Authorization: `Bearer ${apiKey}`, }, }) }) - it.each([['QueryParameter' as keyof typeof AuthenticationMode], [AuthenticationMode.QueryParameter]])( - 'should put the API key in the query parameters for AuthenticationMode.QueryParameter', - async (authenticationMode) => { - const mockFetch = jest.fn().mockResolvedValue(new Response(JSON.stringify({}))) - - const client = new FingerprintJsServerApiClient({ - fetch: mockFetch, - apiKey: 'test', - region: Region.Global, - authenticationMode, - }) - - await client.getVisits('visitorId') - - expect(mockFetch).toHaveBeenCalledTimes(1) - expect(mockFetch).toHaveBeenCalledWith(expect.stringMatching(/.*\?.*api_key=test.*$/), { - method: 'GET', - headers: undefined, - }) + it('should throw SdkError if fetch fails', async () => { + const mockFetch = jest.fn().mockRejectedValue(new Error('fetch error')) + + const client = new FingerprintServerApiClient({ + fetch: mockFetch, + apiKey: 'test', + }) + + await expect(client.getEvent('')).rejects.toBeInstanceOf(SdkError) + }) + + it('throws SdkError when response has invalid json', async () => { + const badJsonOk = { + ok: true, + status: 200, + headers: new Headers({ 'content-type': 'application/json' }), + json: jest.fn().mockRejectedValue(new SyntaxError('Unexpected token')), + clone: jest.fn(), + } + badJsonOk.clone.mockReturnValue(badJsonOk) + + const mockFetch = jest.fn().mockResolvedValue(badJsonOk as unknown as Response) + + const client = new FingerprintServerApiClient({ + fetch: mockFetch, + apiKey: 'test', + }) + + await expect(client.getEvent('')).rejects.toThrow('Failed to parse JSON response') + + await expect(client.getEvent('')).rejects.toBeInstanceOf(SdkError) + + await expect(client.getEvent('')).rejects.toMatchObject({ cause: expect.any(SyntaxError) }) + }) + + it('throws SdkError when error response has invalid json', async () => { + const badJsonFail = { + ok: false, + status: 500, + headers: new Headers({ 'content-type': 'text/plain' }), + json: jest.fn().mockRejectedValue('Unexpected error format'), + clone: jest.fn(), } - ) + + badJsonFail.clone.mockReturnValue(badJsonFail) + + const mockFetch = jest.fn().mockResolvedValue(badJsonFail as unknown as Response) + + const client = new FingerprintServerApiClient({ + fetch: mockFetch, + apiKey: 'test', + }) + + await expect(client.getEvent('')).rejects.toThrow('Failed to parse JSON response') + + await expect(client.getEvent('')).rejects.toBeInstanceOf(SdkError) + + await expect(client.getEvent('')).rejects.toMatchObject({ cause: Error('Unexpected error format') }) + }) }) diff --git a/tests/unit-tests/urlUtilsTests.spec.ts b/tests/unit-tests/urlUtilsTests.spec.ts index 7ce8cfc8..490a65f9 100644 --- a/tests/unit-tests/urlUtilsTests.spec.ts +++ b/tests/unit-tests/urlUtilsTests.spec.ts @@ -1,204 +1,112 @@ -import { Region, VisitorHistoryFilter } from '../../src/types' -import { getRequestPath } from '../../src/urlUtils' +import { Region, SearchEventsFilter } from '../../src' import { version } from '../../package.json' +import { getRequestPath } from '../../src/urlUtils' const visitorId = 'TaDnMBz9XCpZNuSzFUqP' -const requestId = '1626550679751.cVc5Pm' +const eventId = '1626550679751.cVc5Pm' const ii = `ii=fingerprint-pro-server-node-sdk%2F${version}` describe('Get Event path', () => { - it('returns correct path without api key', () => { - const url = getRequestPath({ - path: '/events/{request_id}', - method: 'get', - pathParams: [requestId], - region: Region.Global, - }) - const expectedPath = `https://api.fpjs.io/events/${requestId}?${ii}` - - expect(url).toEqual(expectedPath) - }) - - it('returns correct path with api key', () => { - const apiKey = 'test-api-key' + it('returns correct path', () => { const url = getRequestPath({ - path: '/events/{request_id}', + path: '/events/{event_id}', method: 'get', - pathParams: [requestId], - apiKey, + pathParams: [eventId], region: Region.Global, }) - const expectedPath = `https://api.fpjs.io/events/${requestId}?${ii}&api_key=${apiKey}` + const expectedPath = `https://api.fpjs.io/v4/events/${eventId}?${ii}` expect(url).toEqual(expectedPath) }) }) -describe('Get Visitors path', () => { - const linkedId = 'makma' +describe('Get Event Search path', () => { + const linkedId = 'linkedId' const limit = 10 - const before = 1626538505244 + const end = 1626538505244 + const start = 1626538505241 const paginationKey = '1683900801733.Ogvu1j' - test('eu region without filter', async () => { - const actualPath = getRequestPath({ - path: '/visitors/{visitor_id}', - method: 'get', - pathParams: [visitorId], - region: Region.EU, - }) - const expectedPath = `https://eu.api.fpjs.io/visitors/TaDnMBz9XCpZNuSzFUqP?${ii}` - expect(actualPath).toEqual(expectedPath) - }) - - test('ap region without filter', async () => { - const actualPath = getRequestPath({ - path: '/visitors/{visitor_id}', - method: 'get', - pathParams: [visitorId], - region: Region.AP, - }) - const expectedPath = `https://ap.api.fpjs.io/visitors/TaDnMBz9XCpZNuSzFUqP?${ii}` - expect(actualPath).toEqual(expectedPath) - }) - - test('without path param', async () => { - expect(() => - getRequestPath({ - path: '/visitors/{visitor_id}', - method: 'get', - pathParams: [], - region: Region.AP, - }) - ).toThrowError('Missing path parameter for visitor_id') - }) - - test('unsupported region', async () => { - expect(() => - getRequestPath({ - path: '/visitors/{visitor_id}', - method: 'get', - pathParams: [visitorId], - // @ts-expect-error - region: 'INVALID', - }) - ).toThrowError('Unsupported region') - }) - - test('eu region with request_id filter', async () => { - const filter: VisitorHistoryFilter = { request_id: requestId } - const actualPath = getRequestPath({ - path: '/visitors/{visitor_id}', - method: 'get', - queryParams: filter, - pathParams: [visitorId], - region: Region.EU, - }) - const expectedPath = `https://eu.api.fpjs.io/visitors/TaDnMBz9XCpZNuSzFUqP?request_id=1626550679751.cVc5Pm&${ii}` - expect(actualPath).toEqual(expectedPath) - }) - - test('eu region with request_id linked_id filters', async () => { - const filter: VisitorHistoryFilter = { request_id: requestId, linked_id: linkedId } + test('eu region with linked_id filters', async () => { + const filter: SearchEventsFilter = { linked_id: linkedId } const actualPath = getRequestPath({ - path: '/visitors/{visitor_id}', + path: '/events', method: 'get', queryParams: filter, - pathParams: [visitorId], region: Region.EU, }) - const expectedPath = `https://eu.api.fpjs.io/visitors/TaDnMBz9XCpZNuSzFUqP?request_id=1626550679751.cVc5Pm&linked_id=makma&${ii}` + const expectedPath = `https://eu.api.fpjs.io/v4/events?linked_id=${linkedId}&${ii}` expect(actualPath).toEqual(expectedPath) }) - test('eu region with request_id, linked_id, limit, before filters', async () => { - const filter: VisitorHistoryFilter = { - request_id: requestId, + test('eu region with linked_id, limit, start, end filters', async () => { + const filter: SearchEventsFilter = { linked_id: linkedId, limit, - before, + start, + end, } const actualPath = getRequestPath({ - path: '/visitors/{visitor_id}', + path: '/events', method: 'get', queryParams: filter, - pathParams: [visitorId], region: Region.EU, }) - const expectedPath = `https://eu.api.fpjs.io/visitors/TaDnMBz9XCpZNuSzFUqP?request_id=1626550679751.cVc5Pm&linked_id=makma&limit=10&before=1626538505244&${ii}` + const expectedPath = `https://eu.api.fpjs.io/v4/events?linked_id=${linkedId}&limit=${limit}&start=${start}&end=${end}&${ii}` expect(actualPath).toEqual(expectedPath) }) - test('eu region with request_id, linked_id, limit, paginationKey filters', async () => { - const filter: VisitorHistoryFilter = { - request_id: requestId, + test('eu region with linked_id, limit, paginationKey filters', async () => { + const filter: SearchEventsFilter = { linked_id: linkedId, limit, - paginationKey, + pagination_key: paginationKey, } const actualPath = getRequestPath({ - path: '/visitors/{visitor_id}', + path: '/events', method: 'get', queryParams: filter, - pathParams: [visitorId], region: Region.EU, }) - const expectedPath = `https://eu.api.fpjs.io/visitors/TaDnMBz9XCpZNuSzFUqP?request_id=1626550679751.cVc5Pm&linked_id=makma&limit=10&paginationKey=1683900801733.Ogvu1j&${ii}` + const expectedPath = `https://eu.api.fpjs.io/v4/events?linked_id=${linkedId}&limit=${limit}&pagination_key=${paginationKey}&${ii}` expect(actualPath).toEqual(expectedPath) }) test('global region without filter', async () => { const actualPath = getRequestPath({ - path: '/visitors/{visitor_id}', - method: 'get', - pathParams: [visitorId], - region: Region.Global, - }) - const expectedPath = `https://api.fpjs.io/visitors/TaDnMBz9XCpZNuSzFUqP?${ii}` - expect(actualPath).toEqual(expectedPath) - }) - - test('global region with request_id filter', async () => { - const filter: VisitorHistoryFilter = { request_id: requestId } - const actualPath = getRequestPath({ - path: '/visitors/{visitor_id}', + path: '/events', method: 'get', - pathParams: [visitorId], region: Region.Global, - queryParams: filter, }) - const expectedPath = `https://api.fpjs.io/visitors/TaDnMBz9XCpZNuSzFUqP?request_id=1626550679751.cVc5Pm&${ii}` + const expectedPath = `https://api.fpjs.io/v4/events?${ii}` expect(actualPath).toEqual(expectedPath) }) - test('global region with request_id linked_id filters', async () => { - const filter: VisitorHistoryFilter = { request_id: requestId, linked_id: linkedId } + test('global region with linked_id filters', async () => { + const filter: SearchEventsFilter = { linked_id: linkedId } const actualPath = getRequestPath({ - path: '/visitors/{visitor_id}', + path: '/events', method: 'get', - pathParams: [visitorId], queryParams: filter, region: Region.Global, }) - const expectedPath = `https://api.fpjs.io/visitors/TaDnMBz9XCpZNuSzFUqP?request_id=1626550679751.cVc5Pm&linked_id=makma&${ii}` + const expectedPath = `https://api.fpjs.io/v4/events?linked_id=${linkedId}&${ii}` expect(actualPath).toEqual(expectedPath) }) - test('global region with request_id, linked_id, limit, paginationKey filters', async () => { - const filter: VisitorHistoryFilter = { - request_id: requestId, + test('global region with linked_id, limit, paginationKey filters', async () => { + const filter: SearchEventsFilter = { linked_id: linkedId, limit, - paginationKey, + pagination_key: paginationKey, } const actualPath = getRequestPath({ - path: '/visitors/{visitor_id}', + path: '/events', method: 'get', - pathParams: [visitorId], region: Region.Global, queryParams: filter, }) - const expectedPath = `https://api.fpjs.io/visitors/TaDnMBz9XCpZNuSzFUqP?request_id=1626550679751.cVc5Pm&linked_id=makma&limit=10&paginationKey=1683900801733.Ogvu1j&${ii}` + const expectedPath = `https://api.fpjs.io/v4/events?linked_id=${linkedId}&limit=${limit}&pagination_key=${paginationKey}&${ii}` expect(actualPath).toEqual(expectedPath) }) }) @@ -211,7 +119,7 @@ describe('Delete visitor path', () => { pathParams: [visitorId], region: Region.EU, }) - const expectedPath = `https://eu.api.fpjs.io/visitors/TaDnMBz9XCpZNuSzFUqP?${ii}` + const expectedPath = `https://eu.api.fpjs.io/v4/visitors/TaDnMBz9XCpZNuSzFUqP?${ii}` expect(actualPath).toEqual(expectedPath) }) @@ -222,7 +130,7 @@ describe('Delete visitor path', () => { pathParams: [visitorId], region: Region.AP, }) - const expectedPath = `https://ap.api.fpjs.io/visitors/TaDnMBz9XCpZNuSzFUqP?${ii}` + const expectedPath = `https://ap.api.fpjs.io/v4/visitors/TaDnMBz9XCpZNuSzFUqP?${ii}` expect(actualPath).toEqual(expectedPath) }) @@ -233,7 +141,58 @@ describe('Delete visitor path', () => { pathParams: [visitorId], region: Region.Global, }) - const expectedPath = `https://api.fpjs.io/visitors/TaDnMBz9XCpZNuSzFUqP?${ii}` + const expectedPath = `https://api.fpjs.io/v4/visitors/TaDnMBz9XCpZNuSzFUqP?${ii}` expect(actualPath).toEqual(expectedPath) }) }) + +describe('getRequestPath', () => { + it('serializes string[] and skips null and undefined', () => { + const actual = getRequestPath({ + path: '/events', + method: 'get', + region: Region.Global, + queryParams: { + environment: ['a', null as unknown as string, 'b', undefined as unknown as string], + linked_id: null as unknown as string, + limit: undefined, + }, + }) + + const expected = `https://api.fpjs.io/v4/events?environment=a&environment=b&${ii}` + expect(actual).toEqual(expected) + }) + + it('throws error on unsupported region', () => { + expect(() => { + getRequestPath({ + path: '/events', + method: 'get', + region: 'unknown' as Region, + }) + }).toThrow('Unsupported region') + }) + + it('throws error when required path param is missing', () => { + expect(() => { + getRequestPath({ + path: '/events/{event_id}', + method: 'get', + pathParams: [], + }) + }).toThrow('Missing path parameter for event_id') + }) + + it('encodes special characters', () => { + const actual = getRequestPath({ + path: '/events', + method: 'get', + queryParams: { + linked_id: 'a b+c%d', + }, + }) + + const expected = `https://api.fpjs.io/v4/events?linked_id=a+b%2Bc%25d&${ii}` + expect(actual).toEqual(expected) + }) +})