From f543622d4d34e64d9c50338ee4ef44d166d39803 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eray=20Ayd=C4=B1n?= Date: Tue, 14 Oct 2025 14:54:13 +0300 Subject: [PATCH 01/29] feat(api-v4)!: migrate SDK to APIv4 - Switch base URLs to APIv4 (`https://{region}.api.fpjs.io/v4`) and add `apiVersion` handling in URL builder. - Use `Authorization: Bearer ` only. Remove custom header and query api authorization. - Event API renamed and updated. Use `event_id` instead of `request_id`. Also use `PATCH` for event update instead of `PUT` method. - Add new examples, mocked responses, and update `sync.sh` script. - README reworked for v4 - Package renamed to `@fingerprint/fingerprint-server-sdk`. Updated description and subpackages (example). - Regenerated types and OpenAPI schema. - Updated unit and mock tests for v4 URLs. BREAKING CHANGES: - Only **Bearer auth** is supported; query and custom-header API-key modes are removed. `AuthenticationMode` option is deleted. - Event endpoint and signatures changes: - Use `client.getEvent(eventId)` instead of `requestId` Related-Task: INTER-1488 --- .changeset/config.json | 2 +- example/.env.example | 4 +- example/deleteVisitor.mjs | 2 +- example/getEvent.mjs | 10 +- example/getVisitorHistory.mjs | 4 +- example/package.json | 4 +- example/relatedVisitors.mjs | 43 - example/searchEvents.mjs | 2 +- example/unsealResult.mjs | 2 +- example/updateEvent.mjs | 14 +- example/validateWebhookSignature.mjs | 2 +- package.json | 6 +- pnpm-lock.yaml | 4 +- readme.md | 42 +- resources/fingerprint-server-api.yaml | 3390 ++++++----------- resources/license_banner.txt | 4 +- src/errors/apiErrors.ts | 18 +- src/errors/handleErrorResponse.ts | 14 +- src/generatedApiTypes.ts | 1902 +++------ src/serverApiClient.ts | 207 +- src/types.ts | 37 +- src/urlUtils.ts | 10 +- sync.sh | 45 +- tests/functional-tests/CHANGELOG.md | 2 +- tests/functional-tests/package.json | 6 +- tests/functional-tests/smokeTests.mjs | 36 +- .../castVisitorWebhookTest.spec.ts | 11 - .../castWebhookEventTest.spec.ts | 11 + .../deleteVisitorDataTests.spec.ts | 6 +- .../getEventTests.spec.ts | 60 +- .../getRelatedVisitorsTests.spec.ts | 146 - .../getVisitorsTests.spec.ts | 148 - .../errors/400_bot_type_invalid.json | 6 - .../errors/400_end_time_invalid.json | 6 - .../errors/400_ip_address_invalid.json | 6 - .../errors/400_limit_invalid.json | 6 - .../errors/400_linked_id_invalid.json | 6 - .../errors/400_pagination_key_invalid.json | 6 - .../errors/400_request_body_invalid.json | 2 +- .../errors/400_reverse_invalid.json | 6 - .../errors/400_start_time_invalid.json | 6 - .../errors/400_visitor_id_invalid.json | 2 +- .../errors/400_visitor_id_required.json | 6 - .../errors/403_feature_not_enabled.json | 2 +- .../errors/403_subscription_not_active.json | 6 - .../errors/403_token_not_found.json | 6 - .../errors/403_token_required.json | 6 - .../errors/403_wrong_region.json | 6 - .../errors/404_event_not_found.json | 6 + .../errors/404_request_not_found.json | 6 - .../errors/404_visitor_not_found.json | 2 +- .../errors/409_state_not_ready.json | 2 +- .../errors/429_too_many_requests.json | 2 +- .../events/get_event_200.json | 178 + .../events/search/get_event_search_200.json | 183 + .../mocked-responses-data/get_event_200.json | 354 -- .../get_event_200_all_errors.json | 164 - .../get_event_200_botd_failed_error.json | 69 - .../get_event_200_extra_fields.json | 91 - ...event_200_identification_failed_error.json | 23 - ...get_event_200_too_many_requests_error.json | 16 - .../get_event_200_with_broken_format.json | 301 -- .../get_event_200_with_unknown_field.json | 299 -- .../get_event_search_200.json | 354 -- .../get_visitors_200_limit_1.json | 61 - .../get_visitors_200_limit_500.json | 3030 --------------- .../get_visitors_400_bad_request.json | 3 - .../get_visitors_403_forbidden.json | 3 - .../get_visitors_429_too_many_requests.json | 3 - .../get_related_visitors_200.json | 10 - .../get_related_visitors_200_empty.json | 3 - .../update_event_multiple_fields_request.json | 7 - .../update_event_one_field_request.json | 3 - .../mocked-responses-data/webhook.json | 293 -- .../webhook/webhook_event.json | 178 + .../searchEventsTests.spec.ts | 88 +- .../updateEventTests.spec.ts | 39 +- tests/unit-tests/serverApiClientTests.spec.ts | 40 +- tests/unit-tests/urlUtilsTests.spec.ts | 130 +- 79 files changed, 2611 insertions(+), 9608 deletions(-) delete mode 100644 example/relatedVisitors.mjs delete mode 100644 tests/mocked-responses-tests/castVisitorWebhookTest.spec.ts create mode 100644 tests/mocked-responses-tests/castWebhookEventTest.spec.ts delete mode 100644 tests/mocked-responses-tests/getRelatedVisitorsTests.spec.ts delete mode 100644 tests/mocked-responses-tests/getVisitorsTests.spec.ts delete mode 100644 tests/mocked-responses-tests/mocked-responses-data/errors/400_bot_type_invalid.json delete mode 100644 tests/mocked-responses-tests/mocked-responses-data/errors/400_end_time_invalid.json delete mode 100644 tests/mocked-responses-tests/mocked-responses-data/errors/400_ip_address_invalid.json delete mode 100644 tests/mocked-responses-tests/mocked-responses-data/errors/400_limit_invalid.json delete mode 100644 tests/mocked-responses-tests/mocked-responses-data/errors/400_linked_id_invalid.json delete mode 100644 tests/mocked-responses-tests/mocked-responses-data/errors/400_pagination_key_invalid.json delete mode 100644 tests/mocked-responses-tests/mocked-responses-data/errors/400_reverse_invalid.json delete mode 100644 tests/mocked-responses-tests/mocked-responses-data/errors/400_start_time_invalid.json delete mode 100644 tests/mocked-responses-tests/mocked-responses-data/errors/400_visitor_id_required.json delete mode 100644 tests/mocked-responses-tests/mocked-responses-data/errors/403_subscription_not_active.json delete mode 100644 tests/mocked-responses-tests/mocked-responses-data/errors/403_token_not_found.json delete mode 100644 tests/mocked-responses-tests/mocked-responses-data/errors/403_token_required.json delete mode 100644 tests/mocked-responses-tests/mocked-responses-data/errors/403_wrong_region.json create mode 100644 tests/mocked-responses-tests/mocked-responses-data/errors/404_event_not_found.json delete mode 100644 tests/mocked-responses-tests/mocked-responses-data/errors/404_request_not_found.json create mode 100644 tests/mocked-responses-tests/mocked-responses-data/events/get_event_200.json create mode 100644 tests/mocked-responses-tests/mocked-responses-data/events/search/get_event_search_200.json delete mode 100644 tests/mocked-responses-tests/mocked-responses-data/get_event_200.json delete mode 100644 tests/mocked-responses-tests/mocked-responses-data/get_event_200_all_errors.json delete mode 100644 tests/mocked-responses-tests/mocked-responses-data/get_event_200_botd_failed_error.json delete mode 100644 tests/mocked-responses-tests/mocked-responses-data/get_event_200_extra_fields.json delete mode 100644 tests/mocked-responses-tests/mocked-responses-data/get_event_200_identification_failed_error.json delete mode 100644 tests/mocked-responses-tests/mocked-responses-data/get_event_200_too_many_requests_error.json delete mode 100644 tests/mocked-responses-tests/mocked-responses-data/get_event_200_with_broken_format.json delete mode 100644 tests/mocked-responses-tests/mocked-responses-data/get_event_200_with_unknown_field.json delete mode 100644 tests/mocked-responses-tests/mocked-responses-data/get_event_search_200.json delete mode 100644 tests/mocked-responses-tests/mocked-responses-data/get_visitors_200_limit_1.json delete mode 100644 tests/mocked-responses-tests/mocked-responses-data/get_visitors_200_limit_500.json delete mode 100644 tests/mocked-responses-tests/mocked-responses-data/get_visitors_400_bad_request.json delete mode 100644 tests/mocked-responses-tests/mocked-responses-data/get_visitors_403_forbidden.json delete mode 100644 tests/mocked-responses-tests/mocked-responses-data/get_visitors_429_too_many_requests.json delete mode 100644 tests/mocked-responses-tests/mocked-responses-data/related-visitors/get_related_visitors_200.json delete mode 100644 tests/mocked-responses-tests/mocked-responses-data/related-visitors/get_related_visitors_200_empty.json delete mode 100644 tests/mocked-responses-tests/mocked-responses-data/update_event_multiple_fields_request.json delete mode 100644 tests/mocked-responses-tests/mocked-responses-data/update_event_one_field_request.json delete mode 100644 tests/mocked-responses-tests/mocked-responses-data/webhook.json create mode 100644 tests/mocked-responses-tests/mocked-responses-data/webhook/webhook_event.json diff --git a/.changeset/config.json b/.changeset/config.json index aabcbd6e..401eea9e 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -12,5 +12,5 @@ "access": "public", "baseBranch": "main", "updateInternalDependencies": "patch", - "ignore": ["fingerprintjs-pro-server-api-node-sdk-example"] + "ignore": ["fingerprint-server-sdk-example"] } diff --git a/example/.env.example b/example/.env.example index de62c8fe..753f0fed 100644 --- a/example/.env.example +++ b/example/.env.example @@ -1,8 +1,8 @@ API_KEY= VISITOR_ID= -REQUEST_ID= +EVENT_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..a5701568 100644 --- a/example/deleteVisitor.mjs +++ b/example/deleteVisitor.mjs @@ -1,4 +1,4 @@ -import { FingerprintJsServerApiClient, Region, RequestError } from '@fingerprintjs/fingerprintjs-pro-server-api' +import { FingerprintJsServerApiClient, Region, RequestError } from '@fingerprint/fingerprint-server-sdk' import { config } from 'dotenv' config() diff --git a/example/getEvent.mjs b/example/getEvent.mjs index 6c2a07f9..b27a916a 100644 --- a/example/getEvent.mjs +++ b/example/getEvent.mjs @@ -1,13 +1,13 @@ -import { FingerprintJsServerApiClient, Region, RequestError } from '@fingerprintjs/fingerprintjs-pro-server-api' +import { FingerprintJsServerApiClient, 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 envRegion = process.env.REGION -if (!requestId) { - console.error('Request ID not defined') +if (!eventId) { + console.error('Event ID not defined') process.exit(1) } @@ -26,7 +26,7 @@ if (envRegion === 'eu') { const client = new FingerprintJsServerApiClient({ region, apiKey }) 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) { diff --git a/example/getVisitorHistory.mjs b/example/getVisitorHistory.mjs index 4d70f43c..6438da9a 100644 --- a/example/getVisitorHistory.mjs +++ b/example/getVisitorHistory.mjs @@ -3,7 +3,7 @@ import { Region, RequestError, TooManyRequestsError, -} from '@fingerprintjs/fingerprintjs-pro-server-api' +} from '@fingerprint/fingerprint-server-sdk' import { config } from 'dotenv' config() @@ -31,7 +31,7 @@ if (envRegion === 'eu') { const client = new FingerprintJsServerApiClient({ region, apiKey }) try { - const visitorHistory = await client.getVisits(visitorId, { limit: 10 }) + const visitorHistory = await client.searchEvents({ visitor_id: visitorId, limit: 10 }) console.log(JSON.stringify(visitorHistory, null, 2)) } catch (error) { if (error instanceof RequestError) { 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..8349a4f5 100644 --- a/example/searchEvents.mjs +++ b/example/searchEvents.mjs @@ -1,4 +1,4 @@ -import { FingerprintJsServerApiClient, Region, RequestError } from '@fingerprintjs/fingerprintjs-pro-server-api' +import { FingerprintJsServerApiClient, Region, RequestError } from '@fingerprint/fingerprint-server-sdk' import { config } from 'dotenv' config() 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..07ae091a 100644 --- a/example/updateEvent.mjs +++ b/example/updateEvent.mjs @@ -1,14 +1,14 @@ -import { FingerprintJsServerApiClient, RequestError, Region } from '@fingerprintjs/fingerprintjs-pro-server-api' +import { FingerprintJsServerApiClient, 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) } @@ -29,13 +29,13 @@ const client = new FingerprintJsServerApiClient({ 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..fd602587 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { - "name": "@fingerprintjs/fingerprintjs-pro-server-api", - "version": "6.10.0", - "description": "Node.js wrapper for FingerprintJS Sever API", + "name": "@fingerprint/fingerprint-server-sdk", + "version": "7.0.0", + "description": "Node.js wrapper for Fingerprint Server API", "main": "dist/index.cjs", "module": "dist/index.mjs", "types": "dist/index.d.ts", 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..9a20e0bb 100644 --- a/readme.md +++ b/readme.md @@ -15,7 +15,7 @@ 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. @@ -55,16 +55,16 @@ Install the package using your favorite package manager: - NPM: ```sh - npm i @fingerprintjs/fingerprintjs-pro-server-api + npm i @fingerprint/fingerprintjs-server-sdk ``` - Yarn: ```sh - yarn add @fingerprintjs/fingerprintjs-pro-server-api + yarn add @fingerprint/fingerprintjs-server-sdk ``` - pnpm: ```sh - pnpm i @fingerprintjs/fingerprintjs-pro-server-api + pnpm i @fingerprint/fingerprintjs-server-sdk ``` ## Getting started @@ -75,7 +75,7 @@ Initialize the client instance and use it to make API requests. You need to spec import { FingerprintJsServerApiClient, Region, -} from '@fingerprintjs/fingerprintjs-pro-server-api' +} from '@fingerprint/fingerprint-server-sdk' const client = new FingerprintJsServerApiClient({ apiKey: '', @@ -83,12 +83,12 @@ const client = new FingerprintJsServerApiClient({ }) // 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) }) @@ -116,7 +116,7 @@ import { RequestError, FingerprintJsServerApiClient, TooManyRequestsError, -} from '@fingerprintjs/fingerprintjs-pro-server-api' +} from '@fingerprint/fingerprint-server-sdk' const client = new FingerprintJsServerApiClient({ apiKey: '', @@ -136,28 +136,6 @@ 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 @@ -167,9 +145,9 @@ try { When handling [Webhooks](https://dev.fingerprint.com/docs/webhooks) coming from Fingerprint, you can cast the payload as the built-in `VisitWebhook` 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 visit = visitWebhookBody as unknown as Event ``` #### Webhook signature validation diff --git a/resources/fingerprint-server-api.yaml b/resources/fingerprint-server-api.yaml index 71a4fa75..5572bf4e 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. + # Overview 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 @@ -25,37 +25,30 @@ tags: description: API documentation url: https://dev.fingerprint.com/reference/pro-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: @@ -63,14 +56,21 @@ paths: description: >- The unique [identifier](https://dev.fingerprint.com/reference/get-function#requestid) - of each identification request. + of each identification request (`requestId` can be used in its + place). 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 +78,58 @@ 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 application's data. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '500': + description: Application 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 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. 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://dev.fingerprint.com/reference/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 +146,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 application's data. content: application/json: schema: @@ -146,29 +157,60 @@ paths: application/json: schema: $ref: '#/components/schemas/ErrorResponse' - /events/search: + /events: get: tags: - Fingerprint operationId: searchEvents - summary: Get events via search + 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: + + + - 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. + + + 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,24 +219,22 @@ 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 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: 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: @@ -202,7 +242,7 @@ paths: description: > Unique [visitor identifier](https://dev.fingerprint.com/reference/get-function#visitorid) - issued by Fingerprint Pro. + issued by Fingerprint Identification and all active Smart Signals. Filter for events matching this `visitor_id`. - name: bot @@ -215,39 +255,51 @@ paths: - bad - none 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 `botd.bot` + property set to a valid value are returned. Events without a `botd` + 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. - All ip_address filters must use CIDR notation, for example, - 10.0.0.0/24, 192.168.0.1/32 + Examples of range based queries: 10.0.0.0/24, 192.168.0.1/32 - 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 + 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 + 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: origin + in: query + schema: + type: string + description: > + Filter events by the origin field of the event. Origin could be the + website domain or mobile app bundle ID (eg: com.foo.bar) - name: start in: query schema: @@ -276,7 +328,7 @@ paths: type: boolean description: > Filter events previously tagged as suspicious via the [Update - API](https://dev.fingerprint.com/reference/updateevent). + API](https://dev.fingerprint.com/reference/updateevent). > Note: When using this parameter, only events with the `suspect` property explicitly set to `true` or `false` are returned. Events @@ -286,143 +338,135 @@ 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 + - high, - medium - low 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 +475,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 +487,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 +498,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 +509,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 +520,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,17 +530,16 @@ 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: @@ -532,64 +550,42 @@ paths: - ios 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: - type: 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. - - name: proximity_precision_radius + - 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: > - 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. + 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. 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 +600,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. - content: - application/json: - schema: - $ref: '#/components/schemas/ErrorPlainResponse' - '403': - description: Forbidden. Access to this API is denied. + '500': + description: Application error. 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 +616,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 @@ -806,16 +654,11 @@ 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` + - If you request [`/v4/events` + API](https://dev.fingerprint.com/reference/getevent) with an `event_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. - ### Interested? @@ -862,243 +705,124 @@ paths: application/json: schema: $ref: '#/components/schemas/ErrorResponse' - /related-visitors: - get: - tags: - - Fingerprint - operationId: getRelatedVisitors - summary: 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). - 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 - 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 +webhooks: + event: + post: + operationId: postEventWebhook + summary: Webhook requestBody: + description: > + If configured, a Webhook event will be posted to the provided + endpoint. The webhook has the same data as our `/v4/events` endpoint. content: application/json: schema: - $ref: '#/components/schemas/Webhook' + $ref: '#/components/schemas/Event' 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. + '200': + description: >- + Return a 200 status to indicate that the data was received + successfully components: securitySchemes: - ApiKeyHeader: - type: apiKey - in: header - name: Auth-API-Key - ApiKeyQuery: - type: apiKey - in: query - name: api_key + 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: - 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: + EventId: + type: string + 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` + 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: >- + 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). + 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 + additionalProperties: false + 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 + description: Contains information about the SDK used to perform the request. additionalProperties: false required: - - isoCode - - name + - platform + - version properties: - isoCode: + platform: type: string - name: + enum: + - js + - android + - ios + - unknown + description: Platform of the SDK used for the identification request. + version: type: string - GeolocationSubdivisions: + 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. + TriggeredBy: type: array + description: The rule(s) associated with triggering the webhook via rule engine. 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 - 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: - 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' - Tag: - type: object - description: >- - A customer-provided value or an object that was sent with identification - request. - additionalProperties: true + type: object + additionalProperties: false + required: + - id + - name + - description + properties: + id: + type: string + name: + type: string + description: + type: string IdentificationConfidence: type: object additionalProperties: false @@ -1113,1076 +837,135 @@ 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: - type: object - additionalProperties: false - required: - - code - - message - properties: - code: - $ref: '#/components/schemas/ErrorCode' - message: - type: string - ProductIdentification: - type: object - additionalProperties: false - properties: - data: - $ref: '#/components/schemas/Identification' - error: - $ref: '#/components/schemas/Error' - BotdBotResult: - 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: + 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 - description: Contains all the information from Bot Detection product 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: - - bot - - url - - ip - - time - - userAgent - - requestId + - visitor_id + - visitor_found properties: - bot: - $ref: '#/components/schemas/BotdBot' - meta: - $ref: '#/components/schemas/Tag' - linkedId: - type: string - description: A customer-provided id that was sent with the request. - url: + visitor_id: type: string - description: Page URL from which the request was sent. - ip: - type: string - description: IP address of the requesting browser or bot. - time: - 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: - type: string - requestId: - 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: - type: object - additionalProperties: false - required: - - result - properties: - result: + 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: > - 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 - properties: - data: - $ref: '#/components/schemas/RootApps' - error: - $ref: '#/components/schemas/Error' - Emulator: - type: object - additionalProperties: false - required: - - result - 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. - ProductEmulator: - type: object - additionalProperties: false - properties: - data: - $ref: '#/components/schemas/Emulator' - error: - $ref: '#/components/schemas/Error' - Geolocation: - type: object - additionalProperties: false - properties: - accuracyRadius: + 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 - 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: - 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: - type: string - name: - type: string - network: - type: string - IPInfoDataCenter: - type: object - additionalProperties: false - required: - - result - - name - properties: - result: - type: boolean - name: - type: string - IPInfoV4: - type: object - additionalProperties: false - required: - - address - - geolocation - properties: - address: - type: string - format: ipv4 - geolocation: - $ref: '#/components/schemas/Geolocation' - asn: - $ref: '#/components/schemas/IPInfoASN' - datacenter: - $ref: '#/components/schemas/IPInfoDataCenter' - IPInfoV6: - type: object - additionalProperties: false - required: - - address - - geolocation - properties: - address: - type: string - format: ipv6 - geolocation: - $ref: '#/components/schemas/Geolocation' - asn: - $ref: '#/components/schemas/IPInfoASN' - datacenter: - $ref: '#/components/schemas/IPInfoDataCenter' - IPInfo: + 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 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: + A customer-provided value or an object that was sent with the + identification request or updated later. + additionalProperties: true + Url: 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' - ProxyConfidence: + description: > + Page URL from which the request was sent. For example + `https://example.com/` + BundleId: 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". - ProxyDetails: - type: object - nullable: true - additionalProperties: false - description: Proxy detection details (present if proxy is detected) - required: - - proxyType - properties: - proxyType: - type: string - enum: - - residential - - data_center - description: > - Residential proxies use real user IP addresses to appear as - legitimate traffic, - - while data center proxies are public proxies hosted in data centers - lastSeenAt: - 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' - Incognito: - 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 - properties: - data: - $ref: '#/components/schemas/Incognito' - error: - $ref: '#/components/schemas/Error' - Tampering: - type: object - additionalProperties: false - required: - - result - - anomalyScore - - antiDetectBrowser - 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: - type: object - additionalProperties: false - properties: - data: - $ref: '#/components/schemas/RawDeviceAttributes' - error: - $ref: '#/components/schemas/Error' - HighActivity: - type: object - additionalProperties: false - 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: - type: object - additionalProperties: false - properties: - data: - $ref: '#/components/schemas/HighActivity' - error: - $ref: '#/components/schemas/Error' - LocationSpoofing: - type: object - additionalProperties: false - required: - - result - properties: - result: - type: boolean - description: >- - Flag indicating whether the request came from a mobile device with - location spoofing enabled. - ProductLocationSpoofing: - type: object - additionalProperties: false - properties: - data: - $ref: '#/components/schemas/LocationSpoofing' - error: - $ref: '#/components/schemas/Error' - SuspectScore: - 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: - type: boolean - 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: - type: object description: > - Is absent if the velocity data could not be generated for the visitor - ID. - additionalProperties: false - required: - - 5m - - 1h - properties: - 5m: - type: integer - 1h: - type: integer - 24h: - 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' - Velocity: - type: object + Bundle Id of the iOS application integrated with the Fingerprint SDK for + the event. For example: `com.foo.app` + PackageName: + type: string 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. - additionalProperties: false - required: - - distinctIp - - distinctLinkedId - - distinctCountry - - events - - ipEvents - - distinctIpByLinkedId - - distinctVisitorIdByLinkedId - 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' - ProductVelocity: - type: object - additionalProperties: false - properties: - data: - $ref: '#/components/schemas/Velocity' - error: - $ref: '#/components/schemas/Error' - DeveloperTools: - type: object - additionalProperties: false - required: - - result - properties: - result: - 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: - 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://dev.fingerprint.com/docs/smart-signals-reference#mitm-attack-detection) - to learn more about this Smart Signal. - ProductMitMAttack: + 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) ....` + BrowserDetails: type: object additionalProperties: false + required: + - browser_name + - browser_full_version + - browser_major_version + - os + - os_version + - device properties: - data: - $ref: '#/components/schemas/MitMAttack' - error: - $ref: '#/components/schemas/Error' + browser_name: + type: string + browser_major_version: + type: string + browser_full_version: + type: string + os: + type: string + os_version: + type: string + device: + type: string Proximity: type: object description: > @@ -2191,14 +974,14 @@ components: additionalProperties: false required: - id - - precisionRadius + - precision_radius - confidence properties: id: type: string description: | A stable privacy-preserving identifier for a given proximity zone. - precisionRadius: + precision_radius: type: integer format: int32 enum: @@ -2223,268 +1006,171 @@ components: 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: + BotResult: + type: string + enum: + - not_detected + - good + - bad + description: | + Bot detection result: + * `not_detected` - 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 + BotType: + type: string + description: | + Additional classification of the bot type if detected. + 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://dev.fingerprint.com/docs/smart-signals-overview#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 - required: - - error - properties: - error: - $ref: '#/components/schemas/Error' - EventsUpdateRequest: - type: object properties: - linkedId: - type: string - description: LinkedID value to assign to the existing event - tag: - $ref: '#/components/schemas/Tag' - suspect: + email_spam: type: boolean - description: Suspect flag indicating observed suspicious or fraudulent event - x-go-force-pointer: true - SearchEventsResponse: + 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 - description: >- - Contains a list of all identification events matching the specified - search criteria. additionalProperties: false properties: - events: + accuracy_radius: + 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 + postal_code: + type: string + timezone: + type: string + format: timezone + city_name: + type: string + country_code: + type: string + minLength: 2 + maxLength: 2 + country_name: + type: string + continent_code: + type: string + minLength: 2 + maxLength: 2 + continent_name: + type: string + subdivisions: type: array items: type: object - description: Device intelligence results for the identification event. + additionalProperties: false required: - - products + - iso_code + - name properties: - products: - $ref: '#/components/schemas/Products' - paginationKey: - type: string - description: >- - Use this value in the `pagination_key` parameter to request the next - page of search results. - Visit: + iso_code: + type: string + name: + type: string + IPInfoV4: type: object additionalProperties: false required: - - requestId - - browserDetails - - incognito - - ip - - timestamp - - time - - url - - tag - - visitorFound - - firstSeenAt - - lastSeenAt + - address properties: - requestId: - 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: + address: type: string - description: IP address of the requesting browser or bot. - ipLocation: - $ref: '#/components/schemas/DeprecatedGeolocation' - linkedId: + format: ipv4 + geolocation: + $ref: '#/components/schemas/Geolocation' + asn: 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: + asn_name: 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: + asn_network: type: string - description: Page URL from which the request was sent. - tag: - $ref: '#/components/schemas/Tag' - confidence: - $ref: '#/components/schemas/IdentificationConfidence' - visitorFound: + datacenter_result: 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: + datacenter_name: + type: string + IPInfoV6: 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 + - address properties: - visitorId: + address: type: string - visits: - 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: + format: ipv6 + geolocation: + $ref: '#/components/schemas/Geolocation' + asn: 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: object - additionalProperties: false - required: - - error - properties: - error: + asn_name: type: string - RelatedVisitor: - type: object - additionalProperties: false - required: - - visitorId - properties: - visitorId: + asn_network: 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: + datacenter_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: + datacenter_name: + type: string + IPInfo: type: object description: >- Details about the request IP address. Has separate fields for v4 and v6 @@ -2495,500 +1181,666 @@ components: $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: - 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' - 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: - type: object - additionalProperties: false - 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. - 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: + 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". + ProxyDetails: type: object additionalProperties: false + description: Proxy detection details (present if `proxy` is `true`) + required: + - proxy_type properties: - time: + proxy_type: type: string - format: date-time + enum: + - residential + - data_center 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). + Residential proxies use real user IP addresses to appear as + legitimate traffic, - See [Factory Reset - Detection](https://dev.fingerprint.com/docs/smart-signals-overview#factory-reset-detection) - to learn more about this Smart Signal. - timestamp: + while data center proxies are public proxies hosted in data centers + last_seen_at: type: integer format: int64 description: > - This field is just another representation of the value in the `time` - field. + Unix millisecond timestamp with hourly resolution of when this IP + was last seen as a proxy + 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://dev.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). - The time of the most recent factory reset that happened on the - **mobile device** is expressed as Unix epoch time. - WebhookJailbroken: + * `false` - No Root Management Apps detected or the client isn't + Android. + 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://dev.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 properties: - result: - type: boolean + anomaly_score: + type: number + format: double + minimum: 0 + maximum: 1 + x-platforms: + - android + - ios + - browser 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: + 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 browser running inside a virtual - machine (e.g. VMWare), `false` otherwise. - WebhookRawDeviceAttributes: + 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: > - 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: - type: object + Is absent if the velocity data could not be generated for the visitor + Id. additionalProperties: false required: - - result + - 5_minutes + - 1_hour properties: - result: - type: boolean - description: Flag indicating if the request came from a high-activity visitor. - dailyRequests: + 5_minutes: 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: + description: > + Count for the last 5 minutes of velocity data, from the time of the + event. + 1_hour: 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 - properties: - result: - type: boolean + Count for the last 1 hour of velocity data, from the time of the + event. + 24_hours: + type: integer description: > - `true` if the request came from a machine being remotely controlled - (e.g. TeamViewer), `false` otherwise. - WebhookVelocity: + 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 `distinct_ip`, `distinct_linked_id`, + `distinct_country`, - The `24h` interval of `distinctIp`, `distinctLinkedId`, - `distinctCountry`, + `distinct_ip_by_linked_id` and `distinct_visitor_id_by_linked_id` will + be omitted - `distinctIpByLinkedId` and `distinctVisitorIdByLinkedId` 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.['24h']`) is higher than 20.000. - hours (`events.intervals.['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. additionalProperties: false 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' - WebhookDeveloperTools: + 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 properties: - result: + timezone_mismatch: + type: boolean + 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` if the browser is Chrome with DevTools open or Firefox with - Developer Tools open, `false` otherwise. - WebhookMitMAttack: + 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. + Event: type: object + description: >- + Contains results from Fingerprint Identification and all active Smart + Signals. 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. + 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 + browser_details: + $ref: '#/components/schemas/BrowserDetails' + x-platforms: + - browser + proximity: + $ref: '#/components/schemas/Proximity' + x-platforms: + - android + - ios + - browser + bot: + $ref: '#/components/schemas/BotResult' + x-platforms: + - browser + bot_type: + $ref: '#/components/schemas/BotType' + 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 + 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 + 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 + 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. - * `false` - Otherwise or when the request originated from a browser. + * `secret_api_key_not_found` - No Fingerprint application found for + specified secret API key. - See [MitM Attack - Detection](https://dev.fingerprint.com/docs/smart-signals-overview#mitm-attack-detection) - to learn more about this Smart Signal. - SupplementaryID: + * `public_api_key_required` - public API key in header is missing or + empty. + + * `public_api_key_not_found` - No Fingerprint application found for + specified public API key. + + * `subscription_not_active` - Fingerprint application is not active. + + * `wrong_region` - Server and application region differ. + + * `feature_not_enabled` - This feature (for example, Delete API) is not + enabled for your application. + + * `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. + 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. + additionalProperties: false 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. + additionalProperties: false 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. 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..e6227163 100644 --- a/src/errors/apiErrors.ts +++ b/src/errors/apiErrors.ts @@ -1,4 +1,4 @@ -import { ErrorPlainResponse, ErrorResponse } from '../types' +import { ErrorResponse } from '../types' import { getRetryAfter } from './getRetryAfter' export class SdkError extends Error { @@ -37,10 +37,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) } @@ -61,16 +57,4 @@ export class TooManyRequestsError extends RequestError<429, ErrorResponse> { 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/handleErrorResponse.ts b/src/errors/handleErrorResponse.ts index 486bd60a..853b713a 100644 --- a/src/errors/handleErrorResponse.ts +++ b/src/errors/handleErrorResponse.ts @@ -1,4 +1,4 @@ -import { ErrorPlainResponse, ErrorResponse } from '../types' +import { ErrorResponse } from '../types' import { RequestError, TooManyRequestsError } from './apiErrors' function isErrorResponse(value: unknown): value is ErrorResponse { @@ -13,10 +13,6 @@ function isErrorResponse(value: unknown): value is ErrorResponse { ) } -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) { @@ -26,13 +22,5 @@ export function handleErrorResponse(json: any, response: Response): never { 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/generatedApiTypes.ts b/src/generatedApiTypes.ts index 2f0dc0c3..f84f9ac3 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,24 @@ 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) * - * 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. + * 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. + * + * Smart Signals not activated for your workspace or are not included in the response. * */ get: operations['searchEvents'] @@ -63,22 +79,13 @@ 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) @@ -96,8 +103,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://dev.fingerprint.com/reference/getevent) 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,34 +115,9 @@ 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': { +} +export interface webhooks { + event: { parameters: { query?: never header?: never @@ -145,424 +126,259 @@ export interface paths { } get?: never put?: never - post?: never + /** Webhook */ + post: operations['postEventWebhook'] 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 - } - } - } + trace?: 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://dev.fingerprint.com/reference/updateevent). */ + 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 + /** @description The rule(s) associated with triggering the webhook via rule engine. */ + TriggeredBy: { + id: string + name: string + description: string + }[] 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 + 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 + * * `not_detected` - 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 * * @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 - /** - * 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. - */ - 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 - } - ProductEmulator: { - data?: components['schemas']['Emulator'] - error?: components['schemas']['Error'] + BotResult: 'not_detected' | 'good' | 'bad' + /** @description Additional classification of the bot type if detected. + * */ + BotType: string + /** @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://dev.fingerprint.com/docs/smart-signals-overview#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 + 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 + 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,746 +386,390 @@ 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. - * */ - result: boolean + last_seen_at?: number } - 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://dev.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 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 + * */ + 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: 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 Confidence score (`0.0 - 1.0`) for tampering detection: + * * Values above `0.5` indicate tampering. + * * Values below `0.5` indicate genuine browsers. * */ - 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. + 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. * */ - antiDetectBrowser: boolean + anti_detect_browser?: 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. + /** @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 - } - ProductClonedApp: { - data?: components['schemas']['ClonedApp'] - error?: components['schemas']['Error'] - } - FactoryReset: { - /** - * 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. - * - */ - 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. - * - */ - 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. + '5_minutes': number + /** @description Count for the last 1 hour of velocity data, from the time of the event. * */ - result: 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. + '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 - } - ProductFrida: { - data?: components['schemas']['Frida'] - error?: components['schemas']['Error'] + '24_hours'?: number } - 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`. + /** @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. * */ - 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. + distinct_ip?: components['schemas']['VelocityData'] + /** @description Is absent if the velocity data could not be generated for the visitor Id. * */ - result: boolean - } - ProductVirtualMachine: { - data?: components['schemas']['VirtualMachine'] - error?: components['schemas']['Error'] - } - 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. + distinct_linked_id?: components['schemas']['VelocityData'] + /** @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_country?: 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. + events?: components['schemas']['VelocityData'] + /** @description Is absent if the velocity data could not be generated for the visitor Id. + * */ + 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. * */ - result: boolean + 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. - * */ - data?: components['schemas']['RemoteControl'] - error?: components['schemas']['Error'] - } - /** @description Is absent if the velocity data could not be generated for the visitor ID. + VpnConfidence: 'low' | 'medium' | 'high' + /** @description Local timezone which is used in timezone_mismatch method. * */ - 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 - } - VelocityData: { - /** @description Is absent if the velocity data could not be generated for the visitor ID. - * */ - intervals?: components['schemas']['VelocityIntervals'] - } - /** @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. + VpnOriginTimezone: string + /** @description Country of the request (only for Android SDK version >= 2.4.0, ISO 3166 format or unknown). * */ - 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: + 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/). * - * - 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. + * * 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. * - * 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. + * This field allows you to differentiate VPN users and relay service users in your fraud prevention logic. * */ - data?: components['schemas']['Velocity'] - error?: components['schemas']['Error'] + relay?: boolean } - DeveloperTools: { - /** @description `true` if the browser is Chrome with DevTools open or Firefox with Developer Tools open, `false` otherwise. + /** @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` * */ - 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. + 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` * */ - 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. + 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://dev.fingerprint.com/reference/updateevent). */ + 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. * */ - 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. + 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/` * */ - 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. + url?: components['schemas']['Url'] + /** @description Bundle Id of the iOS application integrated with the Fingerprint SDK for the event. For example: `com.foo.app` * */ - 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 + 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'] + 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: + * * `not_detected` - 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 + * */ + bot?: components['schemas']['BotResult'] + /** @description Additional classification of the bot type if detected. + * */ + bot_type?: components['schemas']['BotType'] + /** @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. * */ - result?: boolean - } - WebhookEmulator: { + 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://dev.fingerprint.com/docs/smart-signals-overview#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://dev.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 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 + * */ + 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: { - /** @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 + vpn_origin_timezone?: components['schemas']['VpnOriginTimezone'] + /** @description Country of the request (only for Android SDK version >= 2.4.0, ISO 3166 format or unknown). * */ - result?: number + vpn_origin_country?: components['schemas']['VpnOriginCountry'] + vpn_methods?: components['schemas']['VpnMethods'] } /** - * @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 application 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 application found for specified public API key. + * * `subscription_not_active` - Fingerprint application is not active. + * * `wrong_region` - Server and application region differ. + * * `feature_not_enabled` - This feature (for example, Delete API) is not enabled for your application. + * * `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. * + * @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' + 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 application 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 application found for specified public API key. + * * `subscription_not_active` - Fingerprint application is not active. + * * `wrong_region` - Server and application region differ. + * * `feature_not_enabled` - This feature (for example, Delete API) is not enabled for your application. + * * `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. * */ - 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 } } responses: never @@ -1325,8 +785,8 @@ export interface operations { query?: never 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://dev.fingerprint.com/reference/get-function#requestid) of each identification request (`requestId` can be used in its place). */ + event_id: string } cookie?: never } @@ -1338,7 +798,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 +819,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 application's data. */ 404: { headers: { [name: string]: unknown @@ -1359,6 +828,15 @@ export interface operations { 'application/json': components['schemas']['ErrorResponse'] } } + /** @description Application error. */ + 500: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } } } updateEvent: { @@ -1366,14 +844,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://dev.fingerprint.com/reference/get-function#event_id). */ + event_id: string } cookie?: never } requestBody: { content: { - 'application/json': components['schemas']['EventsUpdateRequest'] + 'application/json': components['schemas']['EventUpdate'] } } responses: { @@ -1402,7 +880,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 application's data. */ 404: { headers: { [name: string]: unknown @@ -1424,19 +902,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 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: * - * 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://dev.fingerprint.com/reference/get-function#visitorid) issued by Fingerprint Identification and all active Smart Signals. * Filter for events matching this `visitor_id`. * */ visitor_id?: string @@ -1445,18 +923,24 @@ 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 `botd.bot` property set to a valid value are returned. Events without a `botd` 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 + /** @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 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://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 Filter events by the URL (`url` property) associated with the event. + * */ + url?: string + /** @description Filter events by the origin field of the event. Origin could be the website domain or mobile app bundle ID (eg: com.foo.bar) + * */ + origin?: string /** @description Filter events with a timestamp greater than the start time, in Unix time (milliseconds). * */ start?: number @@ -1471,108 +955,95 @@ export interface operations { * */ 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?: 'high,' | 'medium' | 'low' /** @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. + /** @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. + /** @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. * */ - 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. - * */ - proximity_precision_radius?: 10 | 25 | 65 | 175 | 450 | 1200 | 3300 | 8500 | 22500 + total_hits?: number } header?: never path?: never @@ -1586,7 +1057,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 +1078,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 Application 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'] } } } @@ -1751,62 +1146,27 @@ export interface operations { } } } - getRelatedVisitors: { + postEventWebhook: { 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 - } + query?: never header?: never path?: never cookie?: never } - requestBody?: never + /** @description If configured, a Webhook event will be posted to the provided endpoint. The webhook has the same data as our `/v4/events` endpoint. + * */ + requestBody: { + content: { + 'application/json': components['schemas']['Event'] + } + } responses: { - /** @description OK. */ + /** @description Return a 200 status to indicate that the data was received successfully */ 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. */ - 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'] - } + content?: never } } } diff --git a/src/serverApiClient.ts b/src/serverApiClient.ts index 3d947d1d..52cc8fa4 100644 --- a/src/serverApiClient.ts +++ b/src/serverApiClient.ts @@ -1,17 +1,12 @@ import { getRequestPath } from './urlUtils' import { - AuthenticationMode, EventsGetResponse, - EventsUpdateRequest, + EventUpdate, FingerprintApi, Options, Region, - RelatedVisitorsFilter, - RelatedVisitorsResponse, SearchEventsFilter, SearchEventsResponse, - VisitorHistoryFilter, - VisitorsResponse, } from './types' import { copyResponseJson } from './responseUtils' import { handleErrorResponse } from './errors/handleErrorResponse' @@ -21,12 +16,8 @@ export class FingerprintJsServerApiClient implements FingerprintApi { public readonly apiKey: string - public readonly authenticationMode: AuthenticationMode - protected readonly fetch: typeof fetch - protected static readonly DEFAULT_RETRY_AFTER = 1 - /** * FingerprintJS server API client used to fetch data from FingerprintJS * @constructor @@ -41,7 +32,6 @@ 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 @@ -50,14 +40,14 @@ export class FingerprintJsServerApiClient implements FingerprintApi { /** * 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 * * @returns {Promise} - promise with event response. For more information, see the [Server API documentation](https://dev.fingerprint.com/reference/getevent). * * @example * ```javascript * client - * .getEvent('') + * .getEvent('') * .then((result) => console.log(result)) * .catch((error) => { * if (error instanceof RequestError) { @@ -68,24 +58,23 @@ export class FingerprintJsServerApiClient implements FingerprintApi { * }) * ``` * */ - public async getEvent(requestId: string): Promise { - if (!requestId) { - throw new TypeError('requestId is not set') + public async getEvent(eventId: string): Promise { + if (!eventId) { + throw new TypeError('eventId is not set') } const url = getRequestPath({ - path: '/events/{request_id}', + path: '/events/{event_id}', region: this.region, - apiKey: this.getQueryApiKey(), - pathParams: [requestId], + pathParams: [eventId], method: 'get', }) - const headers = this.getHeaders() - const response = await this.fetch(url, { method: 'GET', - headers, + headers: { + Authorization: `Bearer ${this.apiKey}`, + }, }) const jsonResponse = await copyResponseJson(response) @@ -98,15 +87,15 @@ export class FingerprintJsServerApiClient implements FingerprintApi { } /** - * 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. * * @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://dev.fingerprint.com/docs/js-agent#eventid). * * @return {Promise} * @@ -118,7 +107,7 @@ export class FingerprintJsServerApiClient implements FingerprintApi { * } * * client - * .updateEvent(body, '') + * .updateEvent(body, '') * .then(() => { * // Event was successfully updated * }) @@ -135,27 +124,28 @@ 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}', + path: '/events/{event_id}', region: this.region, - apiKey: this.getQueryApiKey(), - pathParams: [requestId], + pathParams: [eventId], method: 'put', }) - const headers = this.getHeaders() const response = await this.fetch(url, { - method: 'PUT', - headers, + method: 'PATCH', + headers: { + Accept: 'application/json', + Authorization: `Bearer ${this.apiKey}`, + }, body: JSON.stringify(body), }) @@ -206,16 +196,15 @@ export class FingerprintJsServerApiClient implements FingerprintApi { const url = getRequestPath({ 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, + headers: { + Authorization: `Bearer ${this.apiKey}`, + }, }) if (response.status === 200) { @@ -227,13 +216,6 @@ export class FingerprintJsServerApiClient implements FingerprintApi { handleErrorResponse(jsonResponse, response) } - /** - * @deprecated Please use {@link FingerprintJsServerApiClient.getVisits} instead - * */ - public async getVisitorHistory(visitorId: string, filter?: VisitorHistoryFilter): Promise { - return this.getVisits(visitorId, filter) - } - /** * Search for identification events, including Smart Signals, using * multiple filtering criteria. If you don't provide `start` or `end` @@ -272,16 +254,16 @@ export class FingerprintJsServerApiClient implements FingerprintApi { * */ async searchEvents(filter: SearchEventsFilter): Promise { const url = getRequestPath({ - path: '/events/search', + path: '/events', region: this.region, - apiKey: this.getQueryApiKey(), method: 'get', queryParams: filter, }) - const headers = this.getHeaders() const response = await this.fetch(url, { method: 'GET', - headers, + headers: { + Authorization: `Bearer ${this.apiKey}`, + }, }) const jsonResponse = await copyResponseJson(response) @@ -292,129 +274,4 @@ export class FingerprintJsServerApiClient implements FingerprintApi { 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') - } - - const url = getRequestPath({ - path: '/visitors/{visitor_id}', - 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 - } - - handleErrorResponse(jsonResponse, response) - } - - /** - * 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() - - const response = await this.fetch(url, { - method: 'GET', - headers, - }) - - const jsonResponse = await copyResponseJson(response) - - if (response.status === 200) { - return jsonResponse as RelatedVisitorsResponse - } - - 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 - } } diff --git a/src/types.ts b/src/types.ts index f684676f..854acc9d 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,11 +18,6 @@ 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 @@ -38,31 +28,26 @@ export interface Options { /** * 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 */ -export type VisitorsResponse = paths['/visitors/{visitor_id}']['get']['responses']['200']['content']['application/json'] - -export type EventsGetResponse = paths['/events/{request_id}']['get']['responses']['200']['content']['application/json'] - -export type RelatedVisitorsResponse = - paths['/related-visitors']['get']['responses']['200']['content']['application/json'] -export type RelatedVisitorsFilter = paths['/related-visitors']['get']['parameters']['query'] +export type EventsGetResponse = paths['/events/{event_id}']['get']['responses']['200']['content']['application/json'] /** * More info: https://dev.fingerprintjs.com/docs/webhooks#identification-webhook-object-format */ -export type Webhook = components['schemas']['Webhook'] +export type Event = components['schemas']['Event'] -export type EventsUpdateRequest = components['schemas']['EventsUpdateRequest'] +export type EventUpdate = components['schemas']['EventUpdate'] // Extract just the `path` parameters as a tuple of strings type ExtractPathParamStrings = Path extends { parameters: { path: infer P } } @@ -78,6 +63,10 @@ export type ExtractQueryParams = Path extends { parameters: { query?: infe : Q // Otherwise, it's required : never // If no query parameters, return never +type WebhookOperationIds = 'postEventWebhook' + +type ClientOperationKeys = Exclude + // Utility type to extract request body from an operation (for POST, PUT, etc.) type ExtractRequestBody = Path extends { requestBody: { content: { 'application/json': infer B } } } ? B : never @@ -90,7 +79,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 +90,5 @@ type ApiMethod = ( ) => Promise> export type FingerprintApi = { - [Path in keyof operations]: ApiMethod + [Operation in ClientOperationKeys]: ApiMethod } diff --git a/src/urlUtils.ts b/src/urlUtils.ts index 5c07864c..400c4227 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/' @@ -90,7 +92,6 @@ type QueryParams = type GetRequestPathOptions = { path: Path method: Method - apiKey?: string region: Region } & PathParams & QueryParams @@ -104,7 +105,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 @@ -116,7 +116,6 @@ type GetRequestPathOptions({ path, pathParams, - apiKey, queryParams, region, // method mention here so that it can be referenced in JSDoc @@ -127,7 +126,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,9 +139,6 @@ export function getRequestPath ts2) { @@ -73,7 +81,7 @@ 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) 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..e609f557 100644 --- a/tests/mocked-responses-tests/deleteVisitorDataTests.spec.ts +++ b/tests/mocked-responses-tests/deleteVisitorDataTests.spec.ts @@ -7,7 +7,7 @@ import { 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' @@ -30,9 +30,9 @@ describe('[Mocked response] Delete visitor data', () => { 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', } ) diff --git a/tests/mocked-responses-tests/getEventTests.spec.ts b/tests/mocked-responses-tests/getEventTests.spec.ts index c3fe2ef7..bb58e384 100644 --- a/tests/mocked-responses-tests/getEventTests.spec.ts +++ b/tests/mocked-responses-tests/getEventTests.spec.ts @@ -1,54 +1,41 @@ -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, + FingerprintJsServerApiClient, + getIntegrationInfo, + Region, + RequestError, + SdkError, +} from '../../src' +import getEventResponse from './mocked-responses-data/events/get_event_200.json' 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 client = new FingerprintJsServerApiClient({ region: Region.EU, apiKey }) - test('with request_id', async () => { + test('with event_id', async () => { mockFetch.mockReturnValue(Promise.resolve(new Response(JSON.stringify(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)))) - - 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) - - expect(response).toEqual(getEventAllErrorsResponse) - }) - test('403 error', async () => { const errorInfo = { error: { - code: 'TokenRequired', + code: 'secret_api_key_required', message: 'secret key is required', }, } satisfies ErrorResponse @@ -56,7 +43,7 @@ describe('[Mocked response] Get Event', () => { status: 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,7 +51,7 @@ 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 @@ -72,24 +59,23 @@ describe('[Mocked response] Get Event', () => { status: 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' + test('Error with unknown', async () => { const mockResponse = new Response( JSON.stringify({ - error: errorInfo, + error: 'Unexpected error format', }), { status: 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('Error with bad JSON', async () => { @@ -98,7 +84,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..5cb733e5 --- /dev/null +++ b/tests/mocked-responses-tests/mocked-responses-data/events/get_event_200.json @@ -0,0 +1,178 @@ +{ + "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) ....", + "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 + }, + "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", + "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", + "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 + }, + "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 +} 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..f72e99d1 --- /dev/null +++ b/tests/mocked-responses-tests/mocked-responses-data/events/search/get_event_search_200.json @@ -0,0 +1,183 @@ +{ + "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) ....", + "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 + }, + "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", + "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", + "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 + }, + "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 + } + ], + "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..09c15380 --- /dev/null +++ b/tests/mocked-responses-tests/mocked-responses-data/webhook/webhook_event.json @@ -0,0 +1,178 @@ +{ + "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) ....", + "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 + }, + "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", + "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", + "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 + }, + "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 +} \ 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..9fa83169 100644 --- a/tests/mocked-responses-tests/searchEventsTests.spec.ts +++ b/tests/mocked-responses-tests/searchEventsTests.spec.ts @@ -1,5 +1,11 @@ -import { ErrorResponse, FingerprintJsServerApiClient, getIntegrationInfo, RequestError } from '../../src' -import getEventsSearch from './mocked-responses-data/get_event_search_200.json' +import { + ErrorResponse, + FingerprintJsServerApiClient, + getIntegrationInfo, + RequestError, + SearchEventsFilter, +} from '../../src' +import getEventsSearch from './mocked-responses-data/events/search/get_event_search_200.json' jest.spyOn(global, 'fetch') @@ -12,14 +18,16 @@ describe('[Mocked response] Search Events', () => { test('without filter', async () => { mockFetch.mockReturnValue(Promise.resolve(new Response(JSON.stringify(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', } ) @@ -28,16 +36,18 @@ describe('[Mocked response] Search Events', () => { test('with filter params passed as undefined', async () => { mockFetch.mockReturnValue(Promise.resolve(new Response(JSON.stringify(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', } ) @@ -46,16 +56,20 @@ describe('[Mocked response] Search Events', () => { test('with partial filter', async () => { mockFetch.mockReturnValue(Promise.resolve(new Response(JSON.stringify(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', } ) @@ -64,7 +78,7 @@ describe('[Mocked response] Search Events', () => { test('with all possible filters', async () => { mockFetch.mockReturnValue(Promise.resolve(new Response(JSON.stringify(getEventsSearch)))) - const response = await client.searchEvents({ + const filters: SearchEventsFilter = { limit: 10, bot: 'all', visitor_id: 'visitor_id', @@ -88,39 +102,49 @@ 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), { @@ -137,8 +161,8 @@ 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), { diff --git a/tests/mocked-responses-tests/updateEventTests.spec.ts b/tests/mocked-responses-tests/updateEventTests.spec.ts index 5809154d..7e9380d3 100644 --- a/tests/mocked-responses-tests/updateEventTests.spec.ts +++ b/tests/mocked-responses-tests/updateEventTests.spec.ts @@ -1,9 +1,15 @@ -import { ErrorResponse, FingerprintJsServerApiClient, getIntegrationInfo, Region, RequestError } from '../../src' -import Error404 from './mocked-responses-data/errors/404_request_not_found.json' +import { + ErrorResponse, + FingerprintJsServerApiClient, + getIntegrationInfo, + 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' jest.spyOn(global, 'fetch') @@ -12,18 +18,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 }) - test('with visitorId', async () => { + test('with eventId', async () => { mockFetch.mockReturnValue(Promise.resolve(new Response())) const body = { linkedId: 'linked_id', suspect: true, } - const response = await client.updateEvent(body, existingVisitorId) + const response = await client.updateEvent(body, existingEventId) expect(response).toBeUndefined() @@ -32,9 +38,9 @@ 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' }, + headers: { Authorization: `Bearer ${apiKey}` }, method: 'PUT', body: JSON.stringify(body), } @@ -51,7 +57,7 @@ describe('[Mocked response] Update event', () => { linkedId: '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) ) }) @@ -66,7 +72,7 @@ describe('[Mocked response] Update event', () => { linkedId: '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) ) }) @@ -81,7 +87,7 @@ describe('[Mocked response] Update event', () => { linkedId: '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) ) }) @@ -96,7 +102,7 @@ describe('[Mocked response] Update event', () => { linkedId: '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) ) }) @@ -111,7 +117,7 @@ describe('[Mocked response] Update event', () => { linkedId: '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,10 +127,9 @@ 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, @@ -137,7 +142,7 @@ describe('[Mocked response] Update event', () => { linkedId: '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/unit-tests/serverApiClientTests.spec.ts b/tests/unit-tests/serverApiClientTests.spec.ts index 15c3389c..58a135b4 100644 --- a/tests/unit-tests/serverApiClientTests.spec.ts +++ b/tests/unit-tests/serverApiClientTests.spec.ts @@ -1,4 +1,4 @@ -import { RequestError, FingerprintJsServerApiClient, Region, AuthenticationMode } from '../../src' +import { RequestError, FingerprintJsServerApiClient, Region } from '../../src' describe('ServerApiClient', () => { it('should support passing custom fetch implementation', async () => { @@ -10,7 +10,7 @@ describe('ServerApiClient', () => { region: Region.Global, }) - await client.getVisits('visitorId') + await client.getEvent('eventId') expect(mockFetch).toHaveBeenCalledTimes(1) }) @@ -22,6 +22,7 @@ describe('ServerApiClient', () => { message: 'feature not enabled', }, } + const mockFetch = jest.fn().mockResolvedValue(new Response(JSON.stringify(responseBody), { status: 403 })) const client = new FingerprintJsServerApiClient({ @@ -57,46 +58,25 @@ describe('ServerApiClient', () => { expect(client).toBeTruthy() }) - it('should support using a string constant for AuthenticationMode.AuthHeader', async () => { + it('should support using a string constant for Authorization header', async () => { const mockFetch = jest.fn().mockResolvedValue(new Response(JSON.stringify({}))) + const apiKey = 'test' + const client = new FingerprintJsServerApiClient({ fetch: mockFetch, - apiKey: 'test', + 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, - }) - } - ) }) diff --git a/tests/unit-tests/urlUtilsTests.spec.ts b/tests/unit-tests/urlUtilsTests.spec.ts index 7ce8cfc8..ecf3ddba 100644 --- a/tests/unit-tests/urlUtilsTests.spec.ts +++ b/tests/unit-tests/urlUtilsTests.spec.ts @@ -1,43 +1,29 @@ -import { Region, VisitorHistoryFilter } from '../../src/types' -import { getRequestPath } from '../../src/urlUtils' +import { Region, getRequestPath, SearchEventsFilter } from '../../src' import { version } from '../../package.json' 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', () => { + it('returns correct path', () => { const url = getRequestPath({ - path: '/events/{request_id}', + path: '/events/{event_id}', method: 'get', - pathParams: [requestId], + pathParams: [eventId], 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' - const url = getRequestPath({ - path: '/events/{request_id}', - method: 'get', - pathParams: [requestId], - apiKey, - 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 () => { @@ -47,7 +33,7 @@ describe('Get Visitors 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) }) @@ -58,7 +44,7 @@ describe('Get Visitors 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) }) @@ -85,120 +71,86 @@ describe('Get Visitors path', () => { ).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 +163,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 +174,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 +185,7 @@ 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) }) }) From 42cd278d9e7fdcb8867adb288d80124527b409b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eray=20Ayd=C4=B1n?= Date: Tue, 14 Oct 2025 15:26:41 +0300 Subject: [PATCH 02/29] feat: remove accept header for updateEvent Remove `Accept: application/json` testing header for `updateEvent` function. Related-Task: INTER-1488 --- src/serverApiClient.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/serverApiClient.ts b/src/serverApiClient.ts index 52cc8fa4..2ab5fe09 100644 --- a/src/serverApiClient.ts +++ b/src/serverApiClient.ts @@ -143,7 +143,6 @@ export class FingerprintJsServerApiClient implements FingerprintApi { const response = await this.fetch(url, { method: 'PATCH', headers: { - Accept: 'application/json', Authorization: `Bearer ${this.apiKey}`, }, body: JSON.stringify(body), From a9286111c11c7d97b1ff10e5b43ce2283b49a111 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eray=20Ayd=C4=B1n?= Date: Tue, 14 Oct 2025 15:27:18 +0300 Subject: [PATCH 03/29] test: fix update event test expected method Fix updateEventTests test expected method to `PATCH`. Related-Task: INTER-1488 --- tests/mocked-responses-tests/updateEventTests.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/mocked-responses-tests/updateEventTests.spec.ts b/tests/mocked-responses-tests/updateEventTests.spec.ts index 7e9380d3..ea5f7718 100644 --- a/tests/mocked-responses-tests/updateEventTests.spec.ts +++ b/tests/mocked-responses-tests/updateEventTests.spec.ts @@ -26,7 +26,7 @@ describe('[Mocked response] Update event', () => { mockFetch.mockReturnValue(Promise.resolve(new Response())) const body = { - linkedId: 'linked_id', + linked_id: 'linked_id', suspect: true, } const response = await client.updateEvent(body, existingEventId) @@ -41,7 +41,7 @@ describe('[Mocked response] Update event', () => { `https://eu.api.fpjs.io/v4/events/${existingEventId}?ii=${encodeURIComponent(getIntegrationInfo())}`, { headers: { Authorization: `Bearer ${apiKey}` }, - method: 'PUT', + method: 'PATCH', body: JSON.stringify(body), } ) From 558c352e15cfaf898337d56086998c67d6a42778 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eray=20Ayd=C4=B1n?= Date: Tue, 14 Oct 2025 16:02:58 +0300 Subject: [PATCH 04/29] chore: remove unnecessary commented line Removed unnecessary commented line. Related-Task: INTER-1488 --- src/types.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/types.ts b/src/types.ts index 854acc9d..76103400 100644 --- a/src/types.ts +++ b/src/types.ts @@ -25,15 +25,11 @@ export interface Options { fetch?: typeof fetch } +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 ErrorResponse = components['schemas']['ErrorResponse'] - export type SearchEventsFilter = paths['/events']['get']['parameters']['query'] export type SearchEventsResponse = components['schemas']['EventSearch'] From c6a02cde4d092933de9fd318444c2f7511bfebf5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eray=20Ayd=C4=B1n?= Date: Wed, 15 Oct 2025 15:05:43 +0300 Subject: [PATCH 05/29] docs: expand `searchEvents` JSDoc with new filters Expands `searchEvents` JSDoc with new filters. Also its align parameter names. --- src/serverApiClient.ts | 42 ++++++++++++++++++++++++++++++++---------- 1 file changed, 32 insertions(+), 10 deletions(-) diff --git a/src/serverApiClient.ts b/src/serverApiClient.ts index 2ab5fe09..848cab73 100644 --- a/src/serverApiClient.ts +++ b/src/serverApiClient.ts @@ -226,30 +226,52 @@ 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({ From 696acb32e74eeb6637bd664d03fabf09c5338f73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eray=20Ayd=C4=B1n?= Date: Wed, 15 Oct 2025 15:27:56 +0300 Subject: [PATCH 06/29] docs: fix example naming for error handling --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 9a20e0bb..e8d0481b 100644 --- a/readme.md +++ b/readme.md @@ -125,7 +125,7 @@ const client = new FingerprintJsServerApiClient({ // 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) { From 3840e16b62bc1c2fad57807c73f011cceafb494e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eray=20Ayd=C4=B1n?= Date: Wed, 15 Oct 2025 15:28:20 +0300 Subject: [PATCH 07/29] chore: update OpenAPI schema version --- .schema-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From ce2854dc064037cd7b2583cb076e671db58d0866 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eray=20Ayd=C4=B1n?= Date: Wed, 15 Oct 2025 15:39:40 +0300 Subject: [PATCH 08/29] chore: add changeset file for changes --- .changeset/early-seas-look.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 .changeset/early-seas-look.md diff --git a/.changeset/early-seas-look.md b/.changeset/early-seas-look.md new file mode 100644 index 00000000..814c9f1c --- /dev/null +++ b/.changeset/early-seas-look.md @@ -0,0 +1,24 @@ +--- +'@fingerprint/fingerprint-server-sdk': major +--- + +**Server APIv3 -> Server APIv4 migration** + +- Switch all endpoints to `/v4/*`. +- Remove `authenticationMode` option when initializing `FingerprintJsServerApiClient`. +- 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** +- `authenticationMode` option removed. +- Endpoints and method signatures changed. + - Use `eventId` instead of `requestId` when triggering `updateEvent()` function. + - Use `eventId` instead of `requestId` when triggering `getEvent()` function. +- 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. From 41876b5d3ce052eb150348e204ed7b21393e79bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eray=20Ayd=C4=B1n?= Date: Wed, 15 Oct 2025 16:29:49 +0300 Subject: [PATCH 09/29] docs: update package name for badges --- readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index e8d0481b..8a9d5cef 100644 --- a/readme.md +++ b/readme.md @@ -10,8 +10,8 @@

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

From 29e84971721d9dc7764312cf75e87f55b4f449d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eray=20Ayd=C4=B1n?= Date: Wed, 15 Oct 2025 16:30:18 +0300 Subject: [PATCH 10/29] docs: use correct package name for installation --- readme.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/readme.md b/readme.md index 8a9d5cef..74864633 100644 --- a/readme.md +++ b/readme.md @@ -55,16 +55,16 @@ Install the package using your favorite package manager: - NPM: ```sh - npm i @fingerprint/fingerprintjs-server-sdk + npm i @fingerprint/fingerprint-server-sdk ``` - Yarn: ```sh - yarn add @fingerprint/fingerprintjs-server-sdk + yarn add @fingerprint/fingerprint-server-sdk ``` - pnpm: ```sh - pnpm i @fingerprint/fingerprintjs-server-sdk + pnpm i @fingerprint/fingerprint-server-sdk ``` ## Getting started From 8e2343ed561ec38f442860c7d740c3e00eb536f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eray=20Ayd=C4=B1n?= Date: Wed, 22 Oct 2025 13:06:25 +0300 Subject: [PATCH 11/29] chore: remove `getVisitorHistory` example file Removes `example/getVisitorHistory.mjs` file because it's not a different endpoint anymore. Related-Task: INTER-1488 --- example/getVisitorHistory.mjs | 53 ----------------------------------- 1 file changed, 53 deletions(-) delete mode 100644 example/getVisitorHistory.mjs diff --git a/example/getVisitorHistory.mjs b/example/getVisitorHistory.mjs deleted file mode 100644 index 6438da9a..00000000 --- a/example/getVisitorHistory.mjs +++ /dev/null @@ -1,53 +0,0 @@ -import { - FingerprintJsServerApiClient, - Region, - RequestError, - TooManyRequestsError, -} from '@fingerprint/fingerprint-server-sdk' -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.searchEvents({ visitor_id: 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`) -} From 174b14d350f0bc0157b94fe3bf2a80250f347020 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eray=20Ayd=C4=B1n?= Date: Wed, 22 Oct 2025 13:14:52 +0300 Subject: [PATCH 12/29] chore: ignore `fingerprint-server-sdk-smoke-tests` Add `fingerprint-server-sdk-smoke-tests` to changeset ignore list. Related-Task: INTER-1488 --- .changeset/config.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/config.json b/.changeset/config.json index 401eea9e..339730c2 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -12,5 +12,5 @@ "access": "public", "baseBranch": "main", "updateInternalDependencies": "patch", - "ignore": ["fingerprint-server-sdk-example"] + "ignore": ["fingerprint-server-sdk-example", "fingerprint-server-sdk-smoke-tests"] } From 385b01b7f9e49422bc3b5e4a4423b73c241a9766 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eray=20Ayd=C4=B1n?= Date: Wed, 22 Oct 2025 13:15:42 +0300 Subject: [PATCH 13/29] chore: changeset mention about package name change Explicitly mention about package name change in changeset file. Related-Task: INTER-1488 --- .changeset/loud-waves-drive.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/loud-waves-drive.md 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` From ef98a926e47a29d24a667cf04f1aa851f900ff90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eray=20Ayd=C4=B1n?= Date: Wed, 22 Oct 2025 13:17:46 +0300 Subject: [PATCH 14/29] fix: use correct method for updating event Use correct `patch` method instead of `put` for updating an event. Related-Task: INTER-1488 --- src/serverApiClient.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/serverApiClient.ts b/src/serverApiClient.ts index 848cab73..6070e65c 100644 --- a/src/serverApiClient.ts +++ b/src/serverApiClient.ts @@ -137,7 +137,7 @@ export class FingerprintJsServerApiClient implements FingerprintApi { path: '/events/{event_id}', region: this.region, pathParams: [eventId], - method: 'put', + method: 'patch', }) const response = await this.fetch(url, { From ba36af25d1340864a74e6853b0728579ce2015db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eray=20Ayd=C4=B1n?= Date: Wed, 22 Oct 2025 13:22:07 +0300 Subject: [PATCH 15/29] fix: use response.ok for all success responses Replace strict `response.status === 200` checks with `response.ok` in fetch handlers. Related-Task: INTER-1488 --- src/serverApiClient.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/serverApiClient.ts b/src/serverApiClient.ts index 6070e65c..eb07e466 100644 --- a/src/serverApiClient.ts +++ b/src/serverApiClient.ts @@ -79,7 +79,7 @@ export class FingerprintJsServerApiClient implements FingerprintApi { const jsonResponse = await copyResponseJson(response) - if (response.status === 200) { + if (response.ok) { return jsonResponse as EventsGetResponse } @@ -148,7 +148,7 @@ export class FingerprintJsServerApiClient implements FingerprintApi { body: JSON.stringify(body), }) - if (response.status === 200) { + if (response.ok) { return } @@ -206,7 +206,7 @@ export class FingerprintJsServerApiClient implements FingerprintApi { }, }) - if (response.status === 200) { + if (response.ok) { return } @@ -289,7 +289,7 @@ export class FingerprintJsServerApiClient implements FingerprintApi { const jsonResponse = await copyResponseJson(response) - if (response.status === 200) { + if (response.ok) { return jsonResponse as SearchEventsResponse } From 4f21b239560808579cdea8a8dfef55e52fc84fe4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eray=20Ayd=C4=B1n?= Date: Wed, 22 Oct 2025 14:41:41 +0300 Subject: [PATCH 16/29] feat: simplify API requests with `callApi` Added a new `callApi()` function to handle request building and `fetch` usage in one place. Replaced all direct `fetch` calls with `callApi`. Introduced `defaultHeaders` options to the client, it's include `Authorization` header and allow extra headers and override of `Authorization` header. Made `region` optional in `GetRequestPathOptions` and it's default to `Region.Global` in `getRequestPath` function. Related-Task: INTER-1488 --- src/serverApiClient.ts | 61 +++++++++++++++++++----------------------- src/types.ts | 5 ++++ src/urlUtils.ts | 6 ++--- 3 files changed, 35 insertions(+), 37 deletions(-) diff --git a/src/serverApiClient.ts b/src/serverApiClient.ts index eb07e466..2c292bfa 100644 --- a/src/serverApiClient.ts +++ b/src/serverApiClient.ts @@ -1,4 +1,4 @@ -import { getRequestPath } from './urlUtils' +import { getRequestPath, GetRequestPathOptions } from './urlUtils' import { EventsGetResponse, EventUpdate, @@ -10,6 +10,7 @@ import { } from './types' import { copyResponseJson } from './responseUtils' import { handleErrorResponse } from './errors/handleErrorResponse' +import { paths } from './generatedApiTypes' export class FingerprintJsServerApiClient implements FingerprintApi { public readonly region: Region @@ -18,6 +19,8 @@ export class FingerprintJsServerApiClient implements FingerprintApi { protected readonly fetch: typeof fetch + private readonly defaultHeaders: Record + /** * FingerprintJS server API client used to fetch data from FingerprintJS * @constructor @@ -35,6 +38,11 @@ export class FingerprintJsServerApiClient implements FingerprintApi { this.apiKey = options.apiKey this.fetch = options.fetch ?? fetch + + this.defaultHeaders = { + Authorization: `Bearer ${this.apiKey}`, + ...options.defaultHeaders, + } } /** @@ -63,20 +71,13 @@ export class FingerprintJsServerApiClient implements FingerprintApi { throw new TypeError('eventId is not set') } - const url = getRequestPath({ + const response = await this.callApi({ path: '/events/{event_id}', region: this.region, pathParams: [eventId], method: 'get', }) - const response = await this.fetch(url, { - method: 'GET', - headers: { - Authorization: `Bearer ${this.apiKey}`, - }, - }) - const jsonResponse = await copyResponseJson(response) if (response.ok) { @@ -133,21 +134,13 @@ export class FingerprintJsServerApiClient implements FingerprintApi { throw new TypeError('eventId is not set') } - const url = getRequestPath({ + const response = await this.callApi({ path: '/events/{event_id}', region: this.region, pathParams: [eventId], method: 'patch', }) - const response = await this.fetch(url, { - method: 'PATCH', - headers: { - Authorization: `Bearer ${this.apiKey}`, - }, - body: JSON.stringify(body), - }) - if (response.ok) { return } @@ -192,20 +185,13 @@ export class FingerprintJsServerApiClient implements FingerprintApi { throw TypeError('VisitorId is not set') } - const url = getRequestPath({ + const response = await this.callApi({ path: '/visitors/{visitor_id}', region: this.region, pathParams: [visitorId], method: 'delete', }) - const response = await this.fetch(url, { - method: 'DELETE', - headers: { - Authorization: `Bearer ${this.apiKey}`, - }, - }) - if (response.ok) { return } @@ -274,18 +260,11 @@ export class FingerprintJsServerApiClient implements FingerprintApi { * @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({ + const response = await this.callApi({ path: '/events', - region: this.region, method: 'get', queryParams: filter, }) - const response = await this.fetch(url, { - method: 'GET', - headers: { - Authorization: `Bearer ${this.apiKey}`, - }, - }) const jsonResponse = await copyResponseJson(response) @@ -295,4 +274,18 @@ export class FingerprintJsServerApiClient implements FingerprintApi { handleErrorResponse(jsonResponse, response) } + + private async callApi( + options: GetRequestPathOptions & { headers?: Record } + ) { + const url = getRequestPath(options) + + return await this.fetch(url, { + method: options.method as string, + headers: { + ...this.defaultHeaders, + ...options.headers, + }, + }) + } } diff --git a/src/types.ts b/src/types.ts index 76103400..a594b112 100644 --- a/src/types.ts +++ b/src/types.ts @@ -23,6 +23,11 @@ export interface Options { * Optional fetch implementation * */ fetch?: typeof fetch + + /** + * Optional default headers + */ + defaultHeaders?: Record } export type ErrorResponse = components['schemas']['ErrorResponse'] diff --git a/src/urlUtils.ts b/src/urlUtils.ts index 400c4227..8229f8e9 100644 --- a/src/urlUtils.ts +++ b/src/urlUtils.ts @@ -89,10 +89,10 @@ type QueryParams = queryParams?: ExtractQueryParams // Optional query params } -type GetRequestPathOptions = { +export type GetRequestPathOptions = { path: Path method: Method - region: Region + region?: Region } & PathParams & QueryParams @@ -140,7 +140,7 @@ export function getRequestPath Date: Wed, 22 Oct 2025 14:50:13 +0300 Subject: [PATCH 17/29] refactor: inline null checks in query serializer Remove `isEmptyValue` helper function and use direct `== null` checks to skip `undefined` or `null` values. Related-Task: INTER-1488 --- src/urlUtils.ts | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/urlUtils.ts b/src/urlUtils.ts index 8229f8e9..365f9c4c 100644 --- a/src/urlUtils.ts +++ b/src/urlUtils.ts @@ -19,23 +19,17 @@ 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)]) From ba552dcf31b958ec81460e75ad181dc38b7d9133 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eray=20Ayd=C4=B1n?= Date: Wed, 22 Oct 2025 15:45:24 +0300 Subject: [PATCH 18/29] chore: use `EVENT_ID` placeholder example dotenv Use correct `EVENT_ID` placeholder for the example `.env.example` dotenv file. Related-Task: INTER-1488 --- example/.env.example | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/.env.example b/example/.env.example index 753f0fed..c48a2f10 100644 --- a/example/.env.example +++ b/example/.env.example @@ -1,6 +1,6 @@ API_KEY= VISITOR_ID= -EVENT_ID= +EVENT_ID= # "eu" or "ap", "us" is the default REGION= WEBHOOK_SIGNATURE_SECRET= From 60156de23de179c3fc8f54fefc4cd6c40ca84ee6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eray=20Ayd=C4=B1n?= Date: Wed, 22 Oct 2025 15:48:03 +0300 Subject: [PATCH 19/29] feat: restrict allowed methods via `AllowedMethod` Add `IsNever` and `NonNeverKeys` utility types to filter out `never` type keys. Introduce and export `AllowedMethod` that excludes specific `parameters` and any `never` type methods. Use this `AllowedMethod` type for `GetRequestPathOptions` type and `getRequestPath` functions. Update related signatures. Related-Task: INTER-1488 --- src/serverApiClient.ts | 4 ++-- src/urlUtils.ts | 10 ++++++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/serverApiClient.ts b/src/serverApiClient.ts index 2c292bfa..5f10018f 100644 --- a/src/serverApiClient.ts +++ b/src/serverApiClient.ts @@ -1,4 +1,4 @@ -import { getRequestPath, GetRequestPathOptions } from './urlUtils' +import { AllowedMethod, getRequestPath, GetRequestPathOptions } from './urlUtils' import { EventsGetResponse, EventUpdate, @@ -275,7 +275,7 @@ export class FingerprintJsServerApiClient implements FingerprintApi { handleErrorResponse(jsonResponse, response) } - private async callApi( + private async callApi>( options: GetRequestPathOptions & { headers?: Record } ) { const url = getRequestPath(options) diff --git a/src/urlUtils.ts b/src/urlUtils.ts index 365f9c4c..5922936a 100644 --- a/src/urlUtils.ts +++ b/src/urlUtils.ts @@ -83,7 +83,13 @@ type QueryParams = queryParams?: ExtractQueryParams // Optional query params } -export type GetRequestPathOptions = { +type IsNever = [Exclude] extends [never] ? true : false +type NonNeverKeys = { + [Key in keyof Type]-?: IsNever extends true ? never : Key +}[keyof Type] +export type AllowedMethod = Exclude, 'parameters'> + +export type GetRequestPathOptions> = { path: Path method: Method region?: Region @@ -107,7 +113,7 @@ export type GetRequestPathOptions({ +export function getRequestPath>({ path, pathParams, queryParams, From 297d67912872e555e0f6c4b0a38aa537155d26aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eray=20Ayd=C4=B1n?= Date: Wed, 22 Oct 2025 15:57:43 +0300 Subject: [PATCH 20/29] fix: normalize HTTP method casing (uppercase) Use `options.method.toUpperCase()` when calling `fetch` to ensure standard HTTP method casing and avoid test fails. Change `AllowedMethod` to make a string literal union. Related-Task: INTER-1488 --- src/serverApiClient.ts | 2 +- src/urlUtils.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/serverApiClient.ts b/src/serverApiClient.ts index 5f10018f..579a6d43 100644 --- a/src/serverApiClient.ts +++ b/src/serverApiClient.ts @@ -281,7 +281,7 @@ export class FingerprintJsServerApiClient implements FingerprintApi { const url = getRequestPath(options) return await this.fetch(url, { - method: options.method as string, + method: options.method.toUpperCase(), headers: { ...this.defaultHeaders, ...options.headers, diff --git a/src/urlUtils.ts b/src/urlUtils.ts index 5922936a..fc1c477d 100644 --- a/src/urlUtils.ts +++ b/src/urlUtils.ts @@ -87,7 +87,7 @@ type IsNever = [Exclude] extends [never] ? true : false type NonNeverKeys = { [Key in keyof Type]-?: IsNever extends true ? never : Key }[keyof Type] -export type AllowedMethod = Exclude, 'parameters'> +export type AllowedMethod = Extract, 'parameters'>, string> export type GetRequestPathOptions> = { path: Path From 577660b70e0bd97b8a01f7f53abced998b405969 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eray=20Ayd=C4=B1n?= Date: Wed, 22 Oct 2025 16:08:03 +0300 Subject: [PATCH 21/29] feat: add body support to `callApi` Extend `callApi` to accept an optional `body?: BodyInit` and forward it to `fetch`. Fix `updateEvent` to send `body`. Related-Task: INTER-1488 --- src/serverApiClient.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/serverApiClient.ts b/src/serverApiClient.ts index 579a6d43..802e9b7c 100644 --- a/src/serverApiClient.ts +++ b/src/serverApiClient.ts @@ -139,6 +139,7 @@ export class FingerprintJsServerApiClient implements FingerprintApi { region: this.region, pathParams: [eventId], method: 'patch', + body: JSON.stringify(body), }) if (response.ok) { @@ -276,7 +277,7 @@ export class FingerprintJsServerApiClient implements FingerprintApi { } private async callApi>( - options: GetRequestPathOptions & { headers?: Record } + options: GetRequestPathOptions & { headers?: Record; body?: BodyInit } ) { const url = getRequestPath(options) @@ -286,6 +287,7 @@ export class FingerprintJsServerApiClient implements FingerprintApi { ...this.defaultHeaders, ...options.headers, }, + body: options.body, }) } } From 6ff4e053c9bfba1f58c2f5708b5d19920d57ef61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eray=20Ayd=C4=B1n?= Date: Wed, 22 Oct 2025 16:09:25 +0300 Subject: [PATCH 22/29] test: remove unnecessary visitor detail tests Removed unnecessary visitor detail tests. Related-Task: INTER-1488 --- tests/unit-tests/urlUtilsTests.spec.ts | 45 -------------------------- 1 file changed, 45 deletions(-) diff --git a/tests/unit-tests/urlUtilsTests.spec.ts b/tests/unit-tests/urlUtilsTests.spec.ts index ecf3ddba..8eb4cbaf 100644 --- a/tests/unit-tests/urlUtilsTests.spec.ts +++ b/tests/unit-tests/urlUtilsTests.spec.ts @@ -26,51 +26,6 @@ describe('Get Event Search path', () => { 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/v4/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/v4/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 linked_id filters', async () => { const filter: SearchEventsFilter = { linked_id: linkedId } const actualPath = getRequestPath({ From a9d0af30fcdb601913634b2d264d1ea61839fc06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eray=20Ayd=C4=B1n?= Date: Wed, 22 Oct 2025 17:28:42 +0300 Subject: [PATCH 23/29] chore: fix missing quote for linked_id Fix missing quote for `linked_id` in `searchEvents.mjs` example file. Related-Task: INTER-1488 --- example/searchEvents.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/searchEvents.mjs b/example/searchEvents.mjs index 8349a4f5..03c6016a 100644 --- a/example/searchEvents.mjs +++ b/example/searchEvents.mjs @@ -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, From bfce2df7e16fb5face4f3e3dae185284086e0b24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eray=20Ayd=C4=B1n?= Date: Wed, 22 Oct 2025 19:54:38 +0300 Subject: [PATCH 24/29] chore: remove redundant await keyword in callApi Removed redundant `await` keyword in `callApi` function. Related-Task: INTER-1488 --- src/serverApiClient.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/serverApiClient.ts b/src/serverApiClient.ts index 802e9b7c..4b57136c 100644 --- a/src/serverApiClient.ts +++ b/src/serverApiClient.ts @@ -281,7 +281,7 @@ export class FingerprintJsServerApiClient implements FingerprintApi { ) { const url = getRequestPath(options) - return await this.fetch(url, { + return this.fetch(url, { method: options.method.toUpperCase(), headers: { ...this.defaultHeaders, From 0e4ff4e10c5693d004636b09777673b4eb24f456 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eray=20Ayd=C4=B1n?= Date: Thu, 23 Oct 2025 14:29:35 +0300 Subject: [PATCH 25/29] refactor: unify response handling and improve type Centralize response parsing and error handling in `callApi` function. Throw consistent `SdkError`, `RequestError`, or `TooManyRequestsError` depends on the case. Added `SuccessJsonOrVoid` to automatically resolve the potential/correct return type for success responses. Simplify all public methods. Related-Task: INTER-1488 --- src/serverApiClient.ts | 97 +++++++++++++++++++++--------------------- src/urlUtils.ts | 34 ++++++++++++++- 2 files changed, 82 insertions(+), 49 deletions(-) diff --git a/src/serverApiClient.ts b/src/serverApiClient.ts index 4b57136c..d3f1c85c 100644 --- a/src/serverApiClient.ts +++ b/src/serverApiClient.ts @@ -1,5 +1,6 @@ -import { AllowedMethod, getRequestPath, GetRequestPathOptions } from './urlUtils' +import { AllowedMethod, getRequestPath, GetRequestPathOptions, SuccessJsonOrVoid } from './urlUtils' import { + ErrorResponse, EventsGetResponse, EventUpdate, FingerprintApi, @@ -8,9 +9,9 @@ import { SearchEventsFilter, SearchEventsResponse, } from './types' -import { copyResponseJson } from './responseUtils' -import { handleErrorResponse } from './errors/handleErrorResponse' import { paths } from './generatedApiTypes' +import { RequestError, SdkError, TooManyRequestsError } from './errors/apiErrors' +import { toError } from './utils' export class FingerprintJsServerApiClient implements FingerprintApi { public readonly region: Region @@ -71,20 +72,12 @@ export class FingerprintJsServerApiClient implements FingerprintApi { throw new TypeError('eventId is not set') } - const response = await this.callApi({ + return this.callApi({ path: '/events/{event_id}', region: this.region, pathParams: [eventId], method: 'get', }) - - const jsonResponse = await copyResponseJson(response) - - if (response.ok) { - return jsonResponse as EventsGetResponse - } - - handleErrorResponse(jsonResponse, response) } /** @@ -134,21 +127,13 @@ export class FingerprintJsServerApiClient implements FingerprintApi { throw new TypeError('eventId is not set') } - const response = await this.callApi({ + return this.callApi({ path: '/events/{event_id}', region: this.region, pathParams: [eventId], method: 'patch', body: JSON.stringify(body), }) - - if (response.ok) { - return - } - - const jsonResponse = await copyResponseJson(response) - - handleErrorResponse(jsonResponse, response) } /** @@ -186,20 +171,12 @@ export class FingerprintJsServerApiClient implements FingerprintApi { throw TypeError('VisitorId is not set') } - const response = await this.callApi({ + return this.callApi({ path: '/visitors/{visitor_id}', region: this.region, pathParams: [visitorId], method: 'delete', }) - - if (response.ok) { - return - } - - const jsonResponse = await copyResponseJson(response) - - handleErrorResponse(jsonResponse, response) } /** @@ -261,33 +238,57 @@ export class FingerprintJsServerApiClient implements FingerprintApi { * @param {string[]|undefined} filter.environment - Filter for events by providing one or more environment IDs (`environment_id` property). * */ async searchEvents(filter: SearchEventsFilter): Promise { - const response = await this.callApi({ + return this.callApi({ path: '/events', method: 'get', queryParams: filter, }) - - const jsonResponse = await copyResponseJson(response) - - if (response.ok) { - return jsonResponse as SearchEventsResponse - } - - handleErrorResponse(jsonResponse, response) } private async callApi>( options: GetRequestPathOptions & { headers?: Record; body?: BodyInit } - ) { + ): Promise> { const url = getRequestPath(options) - return this.fetch(url, { - method: options.method.toUpperCase(), - headers: { - ...this.defaultHeaders, - ...options.headers, - }, - body: options.body, - }) + 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) + } + + const contentType = response.headers.get('content-type') ?? '' + const isJson = contentType.includes('application/json') + + if (response.ok) { + if (!isJson || response.status === 204) { + return undefined as SuccessJsonOrVoid + } + let data + try { + data = await response.json() + } catch (e) { + throw new SdkError('Failed to parse JSON response', response, toError(e)) + } + return data as SuccessJsonOrVoid + } + + if (!isJson) { + throw new SdkError(`Non-JSON error response (status ${response.status})`) + } + + // TODO: Use ErrorJson instead of ErrorResponse type. It requires generic error classes without error.message and error.code + const errPayload = (await response.json()) as ErrorResponse + if (response.status === 429) { + throw new TooManyRequestsError(errPayload, response) + } + throw new RequestError(errPayload.error.message, errPayload, response.status, errPayload.error.code, response) } } diff --git a/src/urlUtils.ts b/src/urlUtils.ts index fc1c477d..e66c3289 100644 --- a/src/urlUtils.ts +++ b/src/urlUtils.ts @@ -84,11 +84,43 @@ type QueryParams = } type IsNever = [Exclude] extends [never] ? true : false -type NonNeverKeys = { +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 ErrorResponses = Omit, keyof Response>> + +type OperationOf> = paths[Path][Method] + +type ResponsesOf> = + OperationOf extends { responses: infer Response } ? Response : never + +type SuccessJson> = UnionJsonFromResponses< + SuccessResponses> +> + +export type ErrorJson> = UnionJsonFromResponses< + ErrorResponses> +> + +export type SuccessJsonOrVoid> = [ + SuccessJson, +] extends [never] + ? void + : SuccessJson + export type GetRequestPathOptions> = { path: Path method: Method From bbf565bf6cb5695298d0896bf2ea8c7c1e81703b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eray=20Ayd=C4=B1n?= Date: Thu, 23 Oct 2025 15:02:23 +0300 Subject: [PATCH 26/29] refactor: simplify error handling Removed `handleErrorResponse` and moved all error logic into `callApi`. Exported `isErrorResponse` to detect valid error payloads. Added `createResponse` helper for creating mock Response object with headers. Related-Task: INTER-1488 --- src/errors/handleErrorResponse.ts | 15 +------------ src/serverApiClient.ts | 20 +++++++++++------- .../getEventTests.spec.ts | 21 +++++++------------ .../searchEventsTests.spec.ts | 17 +++++++-------- tests/mocked-responses-tests/utils.ts | 7 +++++++ 5 files changed, 35 insertions(+), 45 deletions(-) create mode 100644 tests/mocked-responses-tests/utils.ts diff --git a/src/errors/handleErrorResponse.ts b/src/errors/handleErrorResponse.ts index 853b713a..6ef2a6f5 100644 --- a/src/errors/handleErrorResponse.ts +++ b/src/errors/handleErrorResponse.ts @@ -1,7 +1,6 @@ import { ErrorResponse } from '../types' -import { RequestError, TooManyRequestsError } from './apiErrors' -function isErrorResponse(value: unknown): value is ErrorResponse { +export function isErrorResponse(value: unknown): value is ErrorResponse { return Boolean( value && typeof value === 'object' && @@ -12,15 +11,3 @@ function isErrorResponse(value: unknown): value is ErrorResponse { 'message' in value.error ) } - -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) - } - - throw RequestError.unknown(response) -} diff --git a/src/serverApiClient.ts b/src/serverApiClient.ts index d3f1c85c..7b5262fa 100644 --- a/src/serverApiClient.ts +++ b/src/serverApiClient.ts @@ -1,6 +1,5 @@ import { AllowedMethod, getRequestPath, GetRequestPathOptions, SuccessJsonOrVoid } from './urlUtils' import { - ErrorResponse, EventsGetResponse, EventUpdate, FingerprintApi, @@ -12,6 +11,7 @@ import { import { paths } from './generatedApiTypes' import { RequestError, SdkError, TooManyRequestsError } from './errors/apiErrors' import { toError } from './utils' +import { isErrorResponse } from './errors/handleErrorResponse' export class FingerprintJsServerApiClient implements FingerprintApi { public readonly region: Region @@ -273,22 +273,26 @@ export class FingerprintJsServerApiClient implements FingerprintApi { } let data try { - data = await response.json() + data = await response.clone().json() } catch (e) { throw new SdkError('Failed to parse JSON response', response, toError(e)) } return data as SuccessJsonOrVoid } - if (!isJson) { - throw new SdkError(`Non-JSON error response (status ${response.status})`) + let errPayload + try { + // TODO: Use ErrorJson instead of ErrorResponse type. It requires generic error classes without error.message and error.code + errPayload = await response.clone().json() + } catch (e) { + throw new SdkError('Failed to parse JSON response', response, toError(e)) } - - // TODO: Use ErrorJson instead of ErrorResponse type. It requires generic error classes without error.message and error.code - const errPayload = (await response.json()) as ErrorResponse if (response.status === 429) { throw new TooManyRequestsError(errPayload, response) } - throw new RequestError(errPayload.error.message, errPayload, response.status, errPayload.error.code, response) + if (isErrorResponse(errPayload)) { + throw new RequestError(errPayload.error.message, errPayload, response.status, errPayload.error.code, response) + } + throw RequestError.unknown(response) } } diff --git a/tests/mocked-responses-tests/getEventTests.spec.ts b/tests/mocked-responses-tests/getEventTests.spec.ts index bb58e384..b0ef3ea7 100644 --- a/tests/mocked-responses-tests/getEventTests.spec.ts +++ b/tests/mocked-responses-tests/getEventTests.spec.ts @@ -7,6 +7,7 @@ import { SdkError, } from '../../src' import getEventResponse from './mocked-responses-data/events/get_event_200.json' +import { createResponse } from './utils' jest.spyOn(global, 'fetch') @@ -18,7 +19,7 @@ describe('[Mocked response] Get Event', () => { const client = new FingerprintJsServerApiClient({ region: Region.EU, apiKey }) test('with event_id', async () => { - mockFetch.mockReturnValue(Promise.resolve(new Response(JSON.stringify(getEventResponse)))) + mockFetch.mockReturnValue(Promise.resolve(createResponse(getEventResponse))) const response = await client.getEvent(existingEventId) @@ -39,9 +40,7 @@ describe('[Mocked response] Get Event', () => { message: 'secret key is required', }, } satisfies ErrorResponse - const mockResponse = new Response(JSON.stringify(errorInfo), { - status: 403, - }) + const mockResponse = createResponse(errorInfo, 403) mockFetch.mockReturnValue(Promise.resolve(mockResponse)) await expect(client.getEvent(existingEventId)).rejects.toThrow( RequestError.fromErrorResponse(errorInfo, mockResponse) @@ -55,9 +54,7 @@ describe('[Mocked response] Get Event', () => { message: 'request id is not found', }, } satisfies ErrorResponse - const mockResponse = new Response(JSON.stringify(errorInfo), { - status: 404, - }) + const mockResponse = createResponse(errorInfo, 404) mockFetch.mockReturnValue(Promise.resolve(mockResponse)) await expect(client.getEvent(existingEventId)).rejects.toThrow( RequestError.fromErrorResponse(errorInfo, mockResponse) @@ -65,13 +62,11 @@ describe('[Mocked response] Get Event', () => { }) test('Error with unknown', async () => { - const mockResponse = new Response( - JSON.stringify({ - error: 'Unexpected error format', - }), + const mockResponse = createResponse( { - status: 404, - } + error: 'Unexpected error format', + }, + 404 ) mockFetch.mockReturnValue(Promise.resolve(mockResponse)) await expect(client.getEvent(existingEventId)).rejects.toThrow(RequestError) diff --git a/tests/mocked-responses-tests/searchEventsTests.spec.ts b/tests/mocked-responses-tests/searchEventsTests.spec.ts index 9fa83169..8ee4edbc 100644 --- a/tests/mocked-responses-tests/searchEventsTests.spec.ts +++ b/tests/mocked-responses-tests/searchEventsTests.spec.ts @@ -6,6 +6,7 @@ import { SearchEventsFilter, } from '../../src' import getEventsSearch from './mocked-responses-data/events/search/get_event_search_200.json' +import { createResponse } from './utils' jest.spyOn(global, 'fetch') @@ -16,7 +17,7 @@ describe('[Mocked response] Search Events', () => { const client = new FingerprintJsServerApiClient({ apiKey }) test('without filter', async () => { - mockFetch.mockReturnValue(Promise.resolve(new Response(JSON.stringify(getEventsSearch)))) + mockFetch.mockReturnValue(Promise.resolve(createResponse(getEventsSearch))) const limit = 10 @@ -34,7 +35,7 @@ describe('[Mocked response] Search Events', () => { }) test('with filter params passed as undefined', async () => { - mockFetch.mockReturnValue(Promise.resolve(new Response(JSON.stringify(getEventsSearch)))) + mockFetch.mockReturnValue(Promise.resolve(createResponse(getEventsSearch))) const limit = 10 @@ -54,7 +55,7 @@ describe('[Mocked response] Search Events', () => { }) test('with partial filter', async () => { - mockFetch.mockReturnValue(Promise.resolve(new Response(JSON.stringify(getEventsSearch)))) + mockFetch.mockReturnValue(Promise.resolve(createResponse(getEventsSearch))) const limit = 10 const bot = 'good' @@ -76,7 +77,7 @@ describe('[Mocked response] Search Events', () => { }) test('with all possible filters', async () => { - mockFetch.mockReturnValue(Promise.resolve(new Response(JSON.stringify(getEventsSearch)))) + mockFetch.mockReturnValue(Promise.resolve(createResponse(getEventsSearch))) const filters: SearchEventsFilter = { limit: 10, @@ -147,9 +148,7 @@ describe('[Mocked response] Search Events', () => { message: 'Forbidden', }, } satisfies ErrorResponse - const mockResponse = new Response(JSON.stringify(error), { - status: 400, - }) + const mockResponse = createResponse(error, 400) mockFetch.mockReturnValue(Promise.resolve(mockResponse)) await expect( client.searchEvents({ @@ -165,9 +164,7 @@ describe('[Mocked response] Search Events', () => { message: 'secret key is required', }, } satisfies ErrorResponse - const mockResponse = new Response(JSON.stringify(error), { - status: 403, - }) + const mockResponse = createResponse(error, 403) mockFetch.mockReturnValue(Promise.resolve(mockResponse)) await expect( client.searchEvents({ diff --git a/tests/mocked-responses-tests/utils.ts b/tests/mocked-responses-tests/utils.ts new file mode 100644 index 00000000..d62a975c --- /dev/null +++ b/tests/mocked-responses-tests/utils.ts @@ -0,0 +1,7 @@ +export const createResponse = (resp: object, status: number = 200) => + new Response(JSON.stringify(resp), { + headers: { + 'content-type': 'application/json', + }, + status, + }) From d967cf23286cf627038df103ec8dcbc071b03b85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eray=20Ayd=C4=B1n?= Date: Wed, 29 Oct 2025 15:27:07 +0300 Subject: [PATCH 27/29] docs: fix webhook example and description Fixes webhook example and description in the `readme.md` file. Related-Task: INTER-1488 --- readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index 74864633..2ca790ed 100644 --- a/readme.md +++ b/readme.md @@ -142,12 +142,12 @@ try { #### 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 { Event } from '@fingerprint/fingerprint-server-sdk' -const visit = visitWebhookBody as unknown as Event +const event = eventWebhookBody as unknown as Event ``` #### Webhook signature validation From ed6fa0190bb12cd9075524d8e7932e5ddfae0ae9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eray=20Ayd=C4=B1n?= Date: Wed, 29 Oct 2025 15:28:42 +0300 Subject: [PATCH 28/29] feat: use Event type instead of EventsGetResponse Use `Event` type instead of duplicated `EventsGetResponse` type. Update sealedResults snapshot and example base64 sealed result value. Fix reference links. Related-Task: INTER-1488 --- src/sealedResults.ts | 13 +- src/serverApiClient.ts | 17 +- src/types.ts | 7 +- .../__snapshots__/sealedResults.spec.ts.snap | 230 ++++++++++++------ tests/unit-tests/sealedResults.spec.ts | 2 +- 5 files changed, 171 insertions(+), 98 deletions(-) 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 7b5262fa..a5012d68 100644 --- a/src/serverApiClient.ts +++ b/src/serverApiClient.ts @@ -1,13 +1,5 @@ import { AllowedMethod, getRequestPath, GetRequestPathOptions, SuccessJsonOrVoid } from './urlUtils' -import { - EventsGetResponse, - EventUpdate, - FingerprintApi, - Options, - Region, - SearchEventsFilter, - SearchEventsResponse, -} from './types' +import { Event, EventUpdate, FingerprintApi, Options, Region, SearchEventsFilter, SearchEventsResponse } from './types' import { paths } from './generatedApiTypes' import { RequestError, SdkError, TooManyRequestsError } from './errors/apiErrors' import { toError } from './utils' @@ -51,7 +43,7 @@ export class FingerprintJsServerApiClient implements FingerprintApi { * * @param eventId - identifier of 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 @@ -67,7 +59,7 @@ export class FingerprintJsServerApiClient implements FingerprintApi { * }) * ``` * */ - public async getEvent(eventId: string): Promise { + public async getEvent(eventId: string): Promise { if (!eventId) { throw new TypeError('eventId is not set') } @@ -89,7 +81,7 @@ export class FingerprintJsServerApiClient implements FingerprintApi { * **Warning** It's not possible to update events older than 10 days. * * @param body - Data to update the event with. - * @param eventId The unique event [identifier](https://dev.fingerprint.com/docs/js-agent#eventid). + * @param eventId The unique event [identifier](https://dev.fingerprint.com/reference/js-agent-get-function#requestid). * * @return {Promise} * @@ -282,7 +274,6 @@ export class FingerprintJsServerApiClient implements FingerprintApi { let errPayload try { - // TODO: Use ErrorJson instead of ErrorResponse type. It requires generic error classes without error.message and error.code errPayload = await response.clone().json() } catch (e) { throw new SdkError('Failed to parse JSON response', response, toError(e)) diff --git a/src/types.ts b/src/types.ts index a594b112..3c7dca46 100644 --- a/src/types.ts +++ b/src/types.ts @@ -39,12 +39,7 @@ export type SearchEventsFilter = paths['/events']['get']['parameters']['query'] export type SearchEventsResponse = components['schemas']['EventSearch'] /** - * More info: https://dev.fingerprintjs.com/docs/server-api#response - */ -export type EventsGetResponse = paths['/events/{event_id}']['get']['responses']['200']['content']['application/json'] - -/** - * More info: https://dev.fingerprintjs.com/docs/webhooks#identification-webhook-object-format + * More info: https://dev.fingerprint.com/reference/server-api-v4-get-event */ export type Event = components['schemas']['Event'] 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') From c073aebe523db351a53ea0da5c3c6d4c092056be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eray=20Ayd=C4=B1n?= Date: Wed, 29 Oct 2025 15:30:07 +0300 Subject: [PATCH 29/29] refactor: remove unnecessary `ErrorJson` type Removes unnecessary `ErrorJson` type from the project. Related-Task: INTER-1488 --- src/urlUtils.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/urlUtils.ts b/src/urlUtils.ts index e66c3289..aa64dc85 100644 --- a/src/urlUtils.ts +++ b/src/urlUtils.ts @@ -100,7 +100,6 @@ type StartingWithSuccessCode = { }[keyof Response] type SuccessResponses = Pick, keyof Response>> -type ErrorResponses = Omit, keyof Response>> type OperationOf> = paths[Path][Method] @@ -111,10 +110,6 @@ type SuccessJson> = SuccessResponses> > -export type ErrorJson> = UnionJsonFromResponses< - ErrorResponses> -> - export type SuccessJsonOrVoid> = [ SuccessJson, ] extends [never]