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/71] 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/71] 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/71] 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/71] 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/71] 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/71] 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/71] 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/71] 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/71] 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/71] 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/71] 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/71] 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/71] 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/71] 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/71] 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/71] 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/71] 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/71] 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/71] 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/71] 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/71] 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/71] 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/71] 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/71] 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/71] 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/71] 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/71] 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/71] 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/71] 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] From c772f6a2add3b277af9071c2f5ef508bd9732262 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eray=20Ayd=C4=B1n?= Date: Wed, 29 Oct 2025 15:40:52 +0300 Subject: [PATCH 30/71] build: revert current version to fix changeset Revert `package.json` version field to old value for fixing changeset issue. Related-Task: INTER-1488 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index fd602587..ed648233 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@fingerprint/fingerprint-server-sdk", - "version": "7.0.0", + "version": "6.10.0", "description": "Node.js wrapper for Fingerprint Server API", "main": "dist/index.cjs", "module": "dist/index.mjs", From ace5e1c623c2212310e882d6e9e3007c5a6d5e0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eray=20Ayd=C4=B1n?= Date: Wed, 29 Oct 2025 15:46:27 +0300 Subject: [PATCH 31/71] feat: remove unused `responseUtils` Removes unused `responseUtils.ts` file from the project. Related-Task: INTER-1488 --- src/responseUtils.ts | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 src/responseUtils.ts diff --git a/src/responseUtils.ts b/src/responseUtils.ts deleted file mode 100644 index f5c59035..00000000 --- a/src/responseUtils.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { SdkError } from './errors/apiErrors' -import { toError } from './utils' - -export async function copyResponseJson(response: Response) { - try { - return await response.clone().json() - } catch (e) { - throw new SdkError('Failed to parse JSON response', response, toError(e)) - } -} From 980ea417ac3c5e13457a487e602273ae38e4b567 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eray=20Ayd=C4=B1n?= Date: Wed, 29 Oct 2025 16:28:35 +0300 Subject: [PATCH 32/71] test: add error test cases for serverApiClient Increases test coverage for `serverApiClient`. Related-Task: INTER-1488 --- tests/unit-tests/serverApiClientTests.spec.ts | 98 ++++++++++++++++++- 1 file changed, 97 insertions(+), 1 deletion(-) diff --git a/tests/unit-tests/serverApiClientTests.spec.ts b/tests/unit-tests/serverApiClientTests.spec.ts index 58a135b4..cb6c411c 100644 --- a/tests/unit-tests/serverApiClientTests.spec.ts +++ b/tests/unit-tests/serverApiClientTests.spec.ts @@ -1,6 +1,12 @@ -import { RequestError, FingerprintJsServerApiClient, Region } from '../../src' +import { RequestError, FingerprintJsServerApiClient, Region, Options, EventUpdate, SdkError } from '../../src' describe('ServerApiClient', () => { + it('should throw error if no token provided', async () => { + expect(() => { + new FingerprintJsServerApiClient({} as Readonly) + }).toThrow('Api key is not set') + }) + it('should support passing custom fetch implementation', async () => { const mockFetch = jest.fn().mockResolvedValue(new Response(JSON.stringify({}))) @@ -58,6 +64,41 @@ describe('ServerApiClient', () => { expect(client).toBeTruthy() }) + it('should throw error when using getEvent if eventId is empty', async () => { + const client = new FingerprintJsServerApiClient({ + apiKey: 'test', + region: 'Global', + }) + + await expect(client.getEvent(undefined as unknown as string)).rejects.toThrow(new TypeError('eventId is not set')) + }) + + it('should throw error when using updateEvent if body or eventId is empty', async () => { + const client = new FingerprintJsServerApiClient({ + apiKey: 'test', + region: 'Global', + }) + + await expect(client.updateEvent(undefined as unknown as EventUpdate, '')).rejects.toThrow( + new TypeError('body is not set') + ) + + await expect(client.updateEvent({ linked_id: '' }, undefined as unknown as string)).rejects.toThrow( + new TypeError('eventId is not set') + ) + }) + + it('should throw error when using deleteVisitorData if visitorId is empty', async () => { + const client = new FingerprintJsServerApiClient({ + apiKey: 'test', + region: 'Global', + }) + + await expect(client.deleteVisitorData(undefined as unknown as string)).rejects.toThrow( + new TypeError('VisitorId is not set') + ) + }) + it('should support using a string constant for Authorization header', async () => { const mockFetch = jest.fn().mockResolvedValue(new Response(JSON.stringify({}))) @@ -79,4 +120,59 @@ describe('ServerApiClient', () => { }, }) }) + + it('should throw SdkError if fetch fails', async () => { + const mockFetch = jest.fn().mockRejectedValue(new Error('fetch error')) + + const client = new FingerprintJsServerApiClient({ + fetch: mockFetch, + apiKey: 'test', + }) + + await expect(client.getEvent('')).rejects.toBeInstanceOf(SdkError) + }) + + it('throws SdkError when ok response has invalid json', async () => { + const badJson = { + ok: true, + status: 200, + headers: new Headers({ 'content-type': 'application/json' }), + json: jest.fn().mockRejectedValue(new SyntaxError('Unexpected token')), + clone: jest.fn(), + } + ;(badJson.clone as jest.Mock).mockReturnValue(badJson) + + const mockFetch = jest.fn().mockResolvedValue(badJson as unknown as Response) + + const client = new FingerprintJsServerApiClient({ + fetch: mockFetch, + apiKey: 'test', + }) + + await expect(client.getEvent('')).rejects.toThrow('Failed to parse JSON response') + + await expect(client.getEvent('')).rejects.toBeInstanceOf(SdkError) + }) + + it('throws SdkError when error response has invalid json', async () => { + const badJson = { + ok: false, + status: 500, + headers: new Headers({ 'content-type': 'text/plain' }), + json: jest.fn().mockRejectedValue(new SyntaxError('Invalid JSON')), + clone: jest.fn(), + } + ;(badJson.clone as jest.Mock).mockReturnValue(badJson) + + const mockFetch = jest.fn().mockResolvedValue(badJson as unknown as Response) + + const client = new FingerprintJsServerApiClient({ + fetch: mockFetch, + apiKey: 'test', + }) + + await expect(client.getEvent('')).rejects.toThrow('Failed to parse JSON response') + + await expect(client.getEvent('')).rejects.toBeInstanceOf(SdkError) + }) }) From 9aeb597f050f1e64356fb966754a3ceb41a1eeb3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eray=20Ayd=C4=B1n?= Date: Wed, 29 Oct 2025 17:08:39 +0300 Subject: [PATCH 33/71] refactor: remove `utils` file Removes `utils.ts` file and move it to `serverApiClient` file. Related-Task: INTER-1488 --- src/serverApiClient.ts | 13 ++++++++++--- src/utils.ts | 7 ------- 2 files changed, 10 insertions(+), 10 deletions(-) delete mode 100644 src/utils.ts diff --git a/src/serverApiClient.ts b/src/serverApiClient.ts index a5012d68..43eaf086 100644 --- a/src/serverApiClient.ts +++ b/src/serverApiClient.ts @@ -2,7 +2,6 @@ import { AllowedMethod, getRequestPath, GetRequestPathOptions, SuccessJsonOrVoid 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' import { isErrorResponse } from './errors/handleErrorResponse' export class FingerprintJsServerApiClient implements FingerprintApi { @@ -267,7 +266,7 @@ export class FingerprintJsServerApiClient implements FingerprintApi { try { data = await response.clone().json() } catch (e) { - throw new SdkError('Failed to parse JSON response', response, toError(e)) + throw new SdkError('Failed to parse JSON response', response, this.toError(e)) } return data as SuccessJsonOrVoid } @@ -276,7 +275,7 @@ export class FingerprintJsServerApiClient implements FingerprintApi { try { errPayload = await response.clone().json() } catch (e) { - throw new SdkError('Failed to parse JSON response', response, toError(e)) + throw new SdkError('Failed to parse JSON response', response, this.toError(e)) } if (response.status === 429) { throw new TooManyRequestsError(errPayload, response) @@ -286,4 +285,12 @@ export class FingerprintJsServerApiClient implements FingerprintApi { } throw RequestError.unknown(response) } + + private toError(e: unknown): Error { + if (e && typeof e === 'object' && 'message' in e) { + return e as Error + } + + return new Error(String(e)) + } } diff --git a/src/utils.ts b/src/utils.ts deleted file mode 100644 index 363b3b64..00000000 --- a/src/utils.ts +++ /dev/null @@ -1,7 +0,0 @@ -export function toError(e: unknown): Error { - if (e && typeof e === 'object' && 'message' in e) { - return e as Error - } - - return new Error(String(e)) -} From 465ae4150669e25cabbb37725e00f1e8573a12eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eray=20Ayd=C4=B1n?= Date: Wed, 29 Oct 2025 17:26:21 +0300 Subject: [PATCH 34/71] test: add error convert coverage Make changes for `throws SdkError` tests in `serverApiClientTests` file to test `toError` functionality. Related-Task: INTER-1488 --- tests/unit-tests/serverApiClientTests.spec.ts | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/tests/unit-tests/serverApiClientTests.spec.ts b/tests/unit-tests/serverApiClientTests.spec.ts index cb6c411c..cfbe40f8 100644 --- a/tests/unit-tests/serverApiClientTests.spec.ts +++ b/tests/unit-tests/serverApiClientTests.spec.ts @@ -132,17 +132,17 @@ describe('ServerApiClient', () => { await expect(client.getEvent('')).rejects.toBeInstanceOf(SdkError) }) - it('throws SdkError when ok response has invalid json', async () => { - const badJson = { + it('throws SdkError when response has invalid json', async () => { + const badJsonOk = { ok: true, status: 200, headers: new Headers({ 'content-type': 'application/json' }), json: jest.fn().mockRejectedValue(new SyntaxError('Unexpected token')), clone: jest.fn(), } - ;(badJson.clone as jest.Mock).mockReturnValue(badJson) + badJsonOk.clone.mockReturnValue(badJsonOk) - const mockFetch = jest.fn().mockResolvedValue(badJson as unknown as Response) + const mockFetch = jest.fn().mockResolvedValue(badJsonOk as unknown as Response) const client = new FingerprintJsServerApiClient({ fetch: mockFetch, @@ -152,19 +152,22 @@ describe('ServerApiClient', () => { await expect(client.getEvent('')).rejects.toThrow('Failed to parse JSON response') await expect(client.getEvent('')).rejects.toBeInstanceOf(SdkError) + + await expect(client.getEvent('')).rejects.toMatchObject({ cause: expect.any(SyntaxError) }) }) it('throws SdkError when error response has invalid json', async () => { - const badJson = { + const badJsonFail = { ok: false, status: 500, headers: new Headers({ 'content-type': 'text/plain' }), - json: jest.fn().mockRejectedValue(new SyntaxError('Invalid JSON')), + json: jest.fn().mockRejectedValue('Unexpected error format'), clone: jest.fn(), } - ;(badJson.clone as jest.Mock).mockReturnValue(badJson) - const mockFetch = jest.fn().mockResolvedValue(badJson as unknown as Response) + badJsonFail.clone.mockReturnValue(badJsonFail) + + const mockFetch = jest.fn().mockResolvedValue(badJsonFail as unknown as Response) const client = new FingerprintJsServerApiClient({ fetch: mockFetch, @@ -174,5 +177,7 @@ describe('ServerApiClient', () => { await expect(client.getEvent('')).rejects.toThrow('Failed to parse JSON response') await expect(client.getEvent('')).rejects.toBeInstanceOf(SdkError) + + await expect(client.getEvent('')).rejects.toMatchObject({ cause: Error('Unexpected error format') }) }) }) From 75f7d5e653d8c9ee5a245c0f4c68bf2cc9ed83ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eray=20Ayd=C4=B1n?= Date: Wed, 29 Oct 2025 17:59:43 +0300 Subject: [PATCH 35/71] refactor: remove unnecessary queryString guard Remove unnecessary length guard in `serializeQueryStringParams` function. Because always `ii` param exists when using this function. Also it's okay to construct `URLSearchParams` with empty array. Related-Task: INTER-1488 --- src/urlUtils.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/urlUtils.ts b/src/urlUtils.ts index aa64dc85..27f53d70 100644 --- a/src/urlUtils.ts +++ b/src/urlUtils.ts @@ -39,10 +39,6 @@ function serializeQueryStringParams(params: QueryStringParameters): string { } } - if (!entries.length) { - return '' - } - const urlSearchParams = new URLSearchParams(entries) return urlSearchParams.toString() From 2eb7507b197e386971990af93e112f94dcc04b10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eray=20Ayd=C4=B1n?= Date: Wed, 29 Oct 2025 18:01:37 +0300 Subject: [PATCH 36/71] test: add errorTests spec and getRequestPath tests Adds `errorTests.spec.ts` to test `TooManyRequestsError` and `getRetryAfter` function. Add extra tests for covering `getRequestPath` method. Related-Task: INTER-1488 --- tests/unit-tests/errorTests.spec.ts | 60 ++++++++++++++++++++++++++ tests/unit-tests/urlUtilsTests.spec.ts | 51 ++++++++++++++++++++++ 2 files changed, 111 insertions(+) create mode 100644 tests/unit-tests/errorTests.spec.ts diff --git a/tests/unit-tests/errorTests.spec.ts b/tests/unit-tests/errorTests.spec.ts new file mode 100644 index 00000000..e65979a6 --- /dev/null +++ b/tests/unit-tests/errorTests.spec.ts @@ -0,0 +1,60 @@ +import { ErrorResponse, getRetryAfter, TooManyRequestsError } from '../../src' + +function makeResponse(retryAfter?: string): Response { + const headers = retryAfter != undefined ? { 'retry-after': retryAfter } : ({} as HeadersInit) + + return new Response(null, { + status: 429, + headers, + }) +} + +describe('getRetryAfter', () => { + it('parses numeric retry-after', () => { + const response = makeResponse('120') + expect(getRetryAfter(response)).toBe(120) + }) + + it('returns 0 if there is no header', () => { + const response = makeResponse() + expect(getRetryAfter(response)).toBe(0) + }) + + it('returns 0 if the value is invalid', () => { + const response = makeResponse('not-a-number') + expect(getRetryAfter(response)).toBe(0) + }) + + it('returns 0 if the value is empty string', () => { + const response = makeResponse('') + expect(getRetryAfter(response)).toBe(0) + }) +}) + +describe('TooManyRequestsError', () => { + const body: ErrorResponse = { + error: { + message: 'Too Many Requests', + code: 'too_many_requests', + }, + } + + it('parses retry-after and include to the error', () => { + const response = makeResponse('60') + const err = new TooManyRequestsError(body, response) + expect(err).toBeInstanceOf(TooManyRequestsError) + expect(err.retryAfter).toBe(60) + }) + + it('use 0 if there is no header', () => { + const response = makeResponse() + const err = new TooManyRequestsError(body, response) + expect(err.retryAfter).toBe(0) + }) + + it('use 0 if the response have invalid value', () => { + const response = makeResponse('not-a-number') + const err = new TooManyRequestsError(body, response) + expect(err.retryAfter).toBe(0) + }) +}) diff --git a/tests/unit-tests/urlUtilsTests.spec.ts b/tests/unit-tests/urlUtilsTests.spec.ts index 8eb4cbaf..7f7804df 100644 --- a/tests/unit-tests/urlUtilsTests.spec.ts +++ b/tests/unit-tests/urlUtilsTests.spec.ts @@ -144,3 +144,54 @@ describe('Delete visitor path', () => { expect(actualPath).toEqual(expectedPath) }) }) + +describe('getRequestPath', () => { + it('serializes string[] and skips null and undefined', () => { + const actual = getRequestPath({ + path: '/events', + method: 'get', + region: Region.Global, + queryParams: { + environment: ['a', null as unknown as string, 'b', undefined as unknown as string], + linked_id: null as unknown as string, + limit: undefined, + }, + }) + + const expected = `https://api.fpjs.io/v4/events?environment%5B%5D=a&environment%5B%5D=b&${ii}` + expect(actual).toEqual(expected) + }) + + it('throws error on unsupported region', () => { + expect(() => { + getRequestPath({ + path: '/events', + method: 'get', + region: 'unknown' as Region, + }) + }).toThrow('Unsupported region') + }) + + it('throws error when required path param is missing', () => { + expect(() => { + getRequestPath({ + path: '/events/{event_id}', + method: 'get', + pathParams: [], + }) + }).toThrow('Missing path parameter for event_id') + }) + + it('encodes special characters', () => { + const actual = getRequestPath({ + path: '/events', + method: 'get', + queryParams: { + linked_id: 'a b+c%d', + }, + }) + + const expected = `https://api.fpjs.io/v4/events?linked_id=a+b%2Bc%25d&${ii}` + expect(actual).toEqual(expected) + }) +}) From 25fe93c8d2288a382a1e0fddd5151d6911c1e682 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eray=20Ayd=C4=B1n?= Date: Thu, 19 Feb 2026 19:02:31 +0300 Subject: [PATCH 37/71] feat: update openapi schema Update OpenAPI schema and re-generate SDK with the latest schema. Related-Task: INTER-1488 --- resources/fingerprint-server-api.yaml | 750 ++++++++++++++++++++++---- src/generatedApiTypes.ts | 502 ++++++++++++++--- 2 files changed, 1071 insertions(+), 181 deletions(-) diff --git a/resources/fingerprint-server-api.yaml b/resources/fingerprint-server-api.yaml index 5572bf4e..7e23e15a 100644 --- a/resources/fingerprint-server-api.yaml +++ b/resources/fingerprint-server-api.yaml @@ -2,9 +2,9 @@ openapi: 3.1.1 info: title: Server API description: > - # 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. + 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. @@ -58,6 +58,17 @@ paths: [identifier](https://dev.fingerprint.com/reference/get-function#requestid) of each identification request (`requestId` can be used in its place). + - name: ruleset_id + in: query + required: false + schema: + type: string + description: > + The ID of the ruleset to evaluate against the event, producing the + action to take for this event. + + The resulting action is returned in the `rule_action` attribute of + the response. responses: '200': description: OK. @@ -78,13 +89,19 @@ paths: schema: $ref: '#/components/schemas/ErrorResponse' '404': - description: Not found. The event Id cannot be found in this application's data. + description: Not found. The event Id cannot be found in this workspace's data. + content: + application/json: + schema: + $ref: '#/components/schemas/ErrorResponse' + '429': + description: Too Many Requests. The request is throttled. content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '500': - description: Application error. + description: Workspace error. content: application/json: schema: @@ -146,7 +163,7 @@ paths: schema: $ref: '#/components/schemas/ErrorResponse' '404': - description: Not found. The event Id cannot be found in this application's data. + description: Not found. The event Id cannot be found in this workspace's data. content: application/json: schema: @@ -162,6 +179,7 @@ paths: tags: - Fingerprint operationId: searchEvents + x-dotnet-use-request-object: true summary: Search events description: > ## Search @@ -186,7 +204,7 @@ paths: range is the **last 7 days**. - ### Filtering events with the`suspect` flag + ### Filtering events with the `suspect` flag The `/v4/events` endpoint unlocks a powerful method for fraud protection @@ -199,6 +217,14 @@ paths: This helps identify patterns of fraudulent behavior. + ### Environment scoping + + + If you use a secret key that is scoped to an environment, you will only + get events associated with the same environment. With a workspace-scoped + environment, you will get events from all environments. + + Smart Signals not activated for your workspace or are not included in the response. parameters: @@ -225,8 +251,9 @@ paths: 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 + added to the response. The pagination key is an arbitrary string + that should not be interpreted in any way and should be passed + as-is. In the following request, use that value in the `pagination_key` parameter to get the next page of results: @@ -248,20 +275,15 @@ paths: - name: bot in: query schema: - type: string - enum: - - all - - good - - bad - - none + $ref: '#/components/schemas/SearchEventsBot' description: > Filter events by the Bot Detection result, specifically: `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 `botd.bot` - property set to a valid value are returned. Events without a `botd` + > Note: When using this parameter, only events with the `bot` + property set to a valid value are returned. Events without a `bot` Smart Signal result are left out of the response. - name: ip_address in: query @@ -273,6 +295,15 @@ paths: assumed. Examples of range based queries: 10.0.0.0/24, 192.168.0.1/32 + - name: asn + in: query + schema: + type: string + description: > + Filter events by the ASN associated with the event's IP address. + + This corresponds to the `ip_info.(v4|v6).asn` property in the + response. - name: linked_id in: query schema: @@ -293,13 +324,26 @@ paths: type: string description: | Filter events by the URL (`url` property) associated with the event. + - name: bundle_id + in: query + schema: + type: string + description: | + Filter events by the Bundle ID (iOS) associated with the event. + - name: package_name + in: query + schema: + type: string + description: > + Filter events by the Package Name (Android) associated with the + event. - name: origin in: query schema: type: string description: > - Filter events by the origin field of the event. Origin could be the - website domain or mobile app bundle ID (eg: com.foo.bar) + Filter events by the origin field of the event. This is applicable + to web events only (e.g., https://example.com) - name: start in: query schema: @@ -460,11 +504,7 @@ paths: - name: vpn_confidence in: query schema: - type: string - enum: - - high, - - medium - - low + $ref: '#/components/schemas/SearchEventsVpnConfidence' description: > Filter events by VPN Detection result confidence level. @@ -543,11 +583,7 @@ paths: - name: sdk_platform in: query schema: - type: string - enum: - - js - - android - - ios + $ref: '#/components/schemas/SearchEventsSdkPlatform' description: > Filter events by the SDK Platform associated with the identification event (`sdk.platform` property) . @@ -568,6 +604,16 @@ paths: items: type: string style: form + - 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 + `proximity.id` property matching the provided ID are returned. + Events without a `proximity` result are left out of the response. - name: total_hits in: query schema: @@ -579,6 +625,16 @@ paths: When set, the response will include a `total_hits` property with a count of total query matches across all pages, up to the specified limit. + - name: tor_node + in: query + schema: + type: boolean + description: > + Filter events by Tor Node detection result. + + > Note: When using this parameter, only events with the `tor_node` + property set to `true` or `false` are returned. Events without a + `tor_node` detection result are left out of the response. responses: '200': description: Events matching the filter(s). @@ -601,7 +657,7 @@ paths: schema: $ref: '#/components/schemas/ErrorResponse' '500': - description: Application error. + description: Workspace error. content: application/json: schema: @@ -692,9 +748,7 @@ paths: schema: $ref: '#/components/schemas/ErrorResponse' '404': - description: >- - Not found. The visitor ID cannot be found in this application's - data. + description: Not found. The visitor ID cannot be found in this workspace's data. content: application/json: schema: @@ -705,24 +759,6 @@ paths: application/json: schema: $ref: '#/components/schemas/ErrorResponse' -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/Event' - responses: - '200': - description: >- - Return a 200 status to indicate that the data was received - successfully components: securitySchemes: bearerAuth: @@ -758,7 +794,6 @@ components: endpoint](https://dev.fingerprint.com/reference/updateevent). Integration: type: object - additionalProperties: false properties: name: type: string @@ -768,7 +803,6 @@ components: description: The version of the specific integration, e.g. "3.11.10". subintegration: type: object - additionalProperties: false properties: name: type: string @@ -779,7 +813,6 @@ components: SDK: type: object description: Contains information about the SDK used to perform the request. - additionalProperties: false required: - platform - version @@ -806,26 +839,8 @@ components: 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: - type: object - additionalProperties: false - required: - - id - - name - - description - properties: - id: - type: string - name: - type: string - description: - type: string IdentificationConfidence: type: object - additionalProperties: false required: - score properties: @@ -847,7 +862,6 @@ components: type: string Identification: type: object - additionalProperties: false required: - visitor_id - visitor_found @@ -878,7 +892,6 @@ components: Corresponding to Wed Sep 17 2025 00:41:46 GMT+0000 SupplementaryIDHighRecall: type: object - additionalProperties: false description: >- A supplementary browser identifier that prioritizes coverage over precision. The High Recall ID algorithm matches more generously, i.e., @@ -943,9 +956,16 @@ components: description: > User Agent of the client, for example: `Mozilla/5.0 (Windows NT 6.1; Win64; x64) ....` + ClientReferrer: + type: string + description: > + Client Referrer field corresponds to the `document.referrer` field + gathered during an identification request. The value is an empty string + if the user navigated to the page directly (not through a link, but, for + example, by using a bookmark) For example: + `https://example.com/blog/my-article` BrowserDetails: type: object - additionalProperties: false required: - browser_name - browser_full_version @@ -971,7 +991,6 @@ components: description: > Proximity ID represents a fixed geographical zone in a discrete global grid within which the device is observed. - additionalProperties: false required: - id - precision_radius @@ -1009,18 +1028,60 @@ components: BotResult: type: string enum: - - not_detected - - good - bad + - good + - not_detected 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 + * `good` - good bot detected, such as Google bot, Baidu Spider, AlexaBot and so on + * `not_detected` - the visitor is not a bot BotType: type: string description: | Additional classification of the bot type if detected. + BotInfo: + type: object + description: Extended bot information. + required: + - category + - provider + - name + - identity + - confidence + properties: + category: + type: string + description: The type and purpose of the bot. + provider: + type: string + description: The organization or company operating the bot. + provider_url: + type: string + description: The URL of the bot provider's website. + name: + type: string + description: The specific name or identifier of the bot. + identity: + type: string + enum: + - verified + - signed + - spoofed + - unknown + description: | + The verification status of the bot's identity: + * `verified` - well-known bot with publicly verifiable identity, directed by the bot provider. + * `signed` - bot that signs its platform via Web Bot Auth, directed by the bot provider’s customers. + * `spoofed` - bot that claims a public identity but fails verification. + * `unknown` - bot that does not publish a verifiable identity. + confidence: + type: string + enum: + - low + - medium + - high + description: Confidence level of the bot identification. ClonedApp: type: boolean description: > @@ -1065,7 +1126,6 @@ components: * `false` - No signs of Frida or the client is not a mobile device. IPBlockList: type: object - additionalProperties: false properties: email_spam: type: boolean @@ -1078,7 +1138,6 @@ components: description: IP address was part of known TOR network activity. Geolocation: type: object - additionalProperties: false properties: accuracy_radius: type: integer @@ -1119,7 +1178,6 @@ components: type: array items: type: object - additionalProperties: false required: - iso_code - name @@ -1130,7 +1188,6 @@ components: type: string IPInfoV4: type: object - additionalProperties: false required: - address properties: @@ -1145,13 +1202,14 @@ components: type: string asn_network: type: string + asn_type: + type: string datacenter_result: type: boolean datacenter_name: type: string IPInfoV6: type: object - additionalProperties: false required: - address properties: @@ -1166,6 +1224,8 @@ components: type: string asn_network: type: string + asn_type: + type: string datacenter_result: type: boolean datacenter_name: @@ -1175,7 +1235,6 @@ components: 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' @@ -1198,7 +1257,6 @@ components: "high". ProxyDetails: type: object - additionalProperties: false description: Proxy detection details (present if `proxy` is `true`) required: - proxy_type @@ -1219,6 +1277,13 @@ components: description: > Unix millisecond timestamp with hourly resolution of when this IP was last seen as a proxy + provider: + type: string + description: > + String representing the last proxy service provider detected when + this + + IP was synced. An IP can be shared by multiple service providers. Incognito: type: boolean description: > @@ -1260,6 +1325,126 @@ components: * `false` - No Root Management Apps detected or the client isn't Android. + RulesetId: + type: string + description: The ID of the evaluated ruleset. + RuleId: + type: string + description: The ID of the rule that matched the identification event. + RuleExpression: + type: string + description: The expression of the rule that matched the identification event. + RuleActionType: + type: string + description: Describes the action to take with the request. + enum: + - allow + - block + RuleActionHeaderField: + type: object + required: + - name + - value + properties: + name: + type: string + description: The header field name. + value: + type: string + description: The value of the header field. + RequestHeaderModifications: + type: object + description: >- + The set of header modifications to apply, in the following order: + remove, set, append. + properties: + remove: + type: array + description: The list of headers to remove. + items: + type: string + set: + type: array + description: >- + The list of headers to set, overwriting any existing headers with + the same name. + items: + $ref: '#/components/schemas/RuleActionHeaderField' + append: + type: array + description: The list of headers to append. + items: + $ref: '#/components/schemas/RuleActionHeaderField' + EventRuleActionAllow: + description: >- + Informs the client that the request should be forwarded to the origin + with optional request header modifications. + type: object + properties: + ruleset_id: + $ref: '#/components/schemas/RulesetId' + rule_id: + $ref: '#/components/schemas/RuleId' + rule_expression: + $ref: '#/components/schemas/RuleExpression' + type: + allOf: + - $ref: '#/components/schemas/RuleActionType' + - const: allow + request_header_modifications: + $ref: '#/components/schemas/RequestHeaderModifications' + required: + - ruleset_id + - type + StatusCode: + type: integer + description: A valid HTTP status code. + RuleActionBody: + type: string + description: The response body to send to the client. + EventRuleActionBlock: + description: >- + Informs the client the request should be blocked using the response + described by this rule action. + type: object + properties: + ruleset_id: + $ref: '#/components/schemas/RulesetId' + rule_id: + $ref: '#/components/schemas/RuleId' + rule_expression: + $ref: '#/components/schemas/RuleExpression' + type: + allOf: + - $ref: '#/components/schemas/RuleActionType' + - const: block + status_code: + $ref: '#/components/schemas/StatusCode' + headers: + type: array + description: A list of headers to send. + items: + $ref: '#/components/schemas/RuleActionHeaderField' + body: + $ref: '#/components/schemas/RuleActionBody' + required: + - ruleset_id + - type + EventRuleAction: + type: object + description: >- + Describes the action the client should take, according to the rule in + the ruleset that matched the event. When getting an event by event ID, + the rule_action will only be included when the ruleset_id query + parameter is specified. + oneOf: + - $ref: '#/components/schemas/EventRuleActionAllow' + - $ref: '#/components/schemas/EventRuleActionBlock' + discriminator: + propertyName: type + mapping: + allow: '#/components/schemas/EventRuleActionAllow' + block: '#/components/schemas/EventRuleActionBlock' SuspectScore: type: integer description: > @@ -1277,7 +1462,6 @@ components: * 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: anomaly_score: type: number @@ -1299,13 +1483,12 @@ components: description: > True if the identified browser resembles an "anti-detect" browser, such as Incognition, which attempts to evade identification by - manipulating its fingerprint. + manipulating its fingerprint. VelocityData: type: object description: > Is absent if the velocity data could not be generated for the visitor Id. - additionalProperties: false required: - 5_minutes - 1_hour @@ -1369,7 +1552,6 @@ components: if the associated event does not have the required data, such as a linked_id. - additionalProperties: false properties: distinct_ip: $ref: '#/components/schemas/VelocityData' @@ -1415,7 +1597,6 @@ components: format or unknown). VpnMethods: type: object - additionalProperties: false properties: timezone_mismatch: type: boolean @@ -1472,12 +1653,305 @@ components: This field allows you to differentiate VPN users and relay service users in your fraud prevention logic. + HighActivity: + type: boolean + description: Flag indicating if the request came from a high-activity visitor. + FontPreferences: + type: object + description: > + Baseline measurement of canonical fonts rendered on the device. Numeric + width metrics, in CSS pixels, for the canonical fonts collected by the + agent. + properties: + default: + type: number + format: double + serif: + type: number + format: double + sans: + type: number + format: double + mono: + type: number + format: double + apple: + type: number + format: double + min: + type: number + format: double + system: + type: number + format: double + Emoji: + type: object + description: Bounding box metrics describing how the emoji glyph renders. + properties: + font: + type: string + description: Font family reported by the browser when drawing the emoji. + width: + type: number + format: double + height: + type: number + format: double + top: + type: number + format: double + bottom: + type: number + format: double + left: + type: number + format: double + right: + type: number + format: double + x: + type: number + format: double + 'y': + type: number + format: double + Fonts: + type: array + description: List of fonts detected on the device. + items: + type: string + example: + - Arial Unicode MS + - Gill Sans + - Helvetica Neue + - Menlo + DeviceMemory: + type: integer + format: int32 + minimum: 0 + example: 8 + description: Rounded amount of RAM (in gigabytes) reported by the browser. + Timezone: + type: string + description: Timezone identifier detected on the client. + Canvas: + type: object + description: Canvas fingerprint containing winding flag plus geometry/text hashes. + properties: + winding: + type: boolean + geometry: + type: string + description: Hash of geometry rendering output or `unsupported` markers. + text: + type: string + description: Hash of text rendering output or `unsupported` markers. + Languages: + type: array + description: > + Navigator languages reported by the agent including fallbacks. Each + inner array represents ordered language preferences reported by + different APIs. + items: + type: array + items: + type: string + WebGlExtensions: + type: object + description: Hashes of WebGL context attributes and extension support. + properties: + context_attributes: + type: string + parameters: + type: string + shader_precisions: + type: string + extensions: + type: string + extension_parameters: + type: string + unsupported_extensions: + type: array + items: + type: string + WebGlBasics: + type: object + description: Render and vendor strings reported by the WebGL context. + properties: + version: + type: string + vendor: + type: string + vendor_unmasked: + type: string + renderer: + type: string + renderer_unmasked: + type: string + shading_language_version: + type: string + ScreenResolution: + type: array + description: Current screen resolution. + minItems: 2 + maxItems: 2 + items: + type: integer + format: int32 + TouchSupport: + type: object + description: Browser-reported touch capabilities. + properties: + touch_event: + type: boolean + touch_start: + type: boolean + max_touch_points: + type: integer + format: int64 + Oscpu: + type: string + description: Navigator `oscpu` string. + Architecture: + type: integer + format: int32 + description: Integer representing the CPU architecture exposed by the browser. + CookiesEnabled: + type: boolean + description: Whether the cookies are enabled in the browser. + HardwareConcurrency: + type: integer + format: int32 + minimum: 1 + description: Number of logical CPU cores reported by the browser. + DateTimeLocale: + type: string + description: > + Locale derived from the Intl.DateTimeFormat API. Negative values + indicate known error states. The negative statuses can be: - "-1": A + permanent status for browsers that don't support Intl API. - "-2": A + permanent status for browsers that don't supportDateTimeFormat + constructor. - "-3": A permanent status for browsers in which + DateTimeFormat locale is undefined or null. + Vendor: + type: string + description: Navigator vendor string. + ColorDepth: + type: integer + format: int32 + description: Screen color depth in bits. + Platform: + type: string + description: Navigator platform string. + SessionStorage: + type: boolean + description: Whether sessionStorage is available. + LocalStorage: + type: boolean + description: Whether localStorage is available. + Audio: + type: number + format: double + description: > + AudioContext fingerprint or negative status when unavailable. The + negative statuses can be: - -1: A permanent status for those browsers + which are known to always suspend audio context - -2: A permanent status + for browsers that don't support the signal - -3: A temporary status that + means that an unexpected timeout has happened + Plugins: + type: array + description: Browser plugins reported by `navigator.plugins`. + items: + type: object + properties: + name: + type: string + description: + type: string + mimeTypes: + type: array + items: + type: object + properties: + type: + type: string + suffixes: + type: string + description: + type: string + required: + - name + IndexedDb: + type: boolean + description: Whether IndexedDB is available. + Math: + type: string + description: Hash of Math APIs used for entropy collection. + RawDeviceAttributes: + type: object + description: > + A curated subset of raw browser/device attributes that the API surface + exposes. Each property contains a value or object with the data for the + collected signal. + properties: + font_preferences: + $ref: '#/components/schemas/FontPreferences' + emoji: + $ref: '#/components/schemas/Emoji' + fonts: + $ref: '#/components/schemas/Fonts' + device_memory: + $ref: '#/components/schemas/DeviceMemory' + timezone: + $ref: '#/components/schemas/Timezone' + canvas: + $ref: '#/components/schemas/Canvas' + languages: + $ref: '#/components/schemas/Languages' + webgl_extensions: + $ref: '#/components/schemas/WebGlExtensions' + webgl_basics: + $ref: '#/components/schemas/WebGlBasics' + screen_resolution: + $ref: '#/components/schemas/ScreenResolution' + touch_support: + $ref: '#/components/schemas/TouchSupport' + oscpu: + $ref: '#/components/schemas/Oscpu' + architecture: + $ref: '#/components/schemas/Architecture' + cookies_enabled: + $ref: '#/components/schemas/CookiesEnabled' + hardware_concurrency: + $ref: '#/components/schemas/HardwareConcurrency' + date_time_locale: + $ref: '#/components/schemas/DateTimeLocale' + vendor: + $ref: '#/components/schemas/Vendor' + color_depth: + $ref: '#/components/schemas/ColorDepth' + platform: + $ref: '#/components/schemas/Platform' + session_storage: + $ref: '#/components/schemas/SessionStorage' + local_storage: + $ref: '#/components/schemas/LocalStorage' + audio: + $ref: '#/components/schemas/Audio' + plugins: + $ref: '#/components/schemas/Plugins' + indexed_db: + $ref: '#/components/schemas/IndexedDb' + math: + $ref: '#/components/schemas/Math' Event: type: object description: >- Contains results from Fingerprint Identification and all active Smart Signals. - additionalProperties: false + required: + - event_id + - timestamp properties: event_id: $ref: '#/components/schemas/EventId' @@ -1563,6 +2037,10 @@ components: - android - ios - browser + client_referrer: + $ref: '#/components/schemas/ClientReferrer' + x-platforms: + - browser browser_details: $ref: '#/components/schemas/BrowserDetails' x-platforms: @@ -1572,7 +2050,6 @@ components: x-platforms: - android - ios - - browser bot: $ref: '#/components/schemas/BotResult' x-platforms: @@ -1581,6 +2058,10 @@ components: $ref: '#/components/schemas/BotType' x-platforms: - browser + bot_info: + $ref: '#/components/schemas/BotInfo' + x-platforms: + - browser cloned_app: $ref: '#/components/schemas/ClonedApp' x-platforms: @@ -1659,6 +2140,8 @@ components: $ref: '#/components/schemas/RootApps' x-platforms: - android + rule_action: + $ref: '#/components/schemas/EventRuleAction' suspect_score: $ref: '#/components/schemas/SuspectScore' x-platforms: @@ -1716,6 +2199,16 @@ components: - android - ios - browser + high_activity_device: + $ref: '#/components/schemas/HighActivity' + x-platforms: + - android + - ios + - browser + raw_device_attributes: + $ref: '#/components/schemas/RawDeviceAttributes' + x-platforms: + - browser ErrorCode: type: string enum: @@ -1735,6 +2228,8 @@ components: - event_not_found - missing_module - payload_too_large + - service_unavailable + - ruleset_not_found description: > Error code: @@ -1744,21 +2239,21 @@ components: * `secret_api_key_required` - secret API key in header is missing or empty. - * `secret_api_key_not_found` - No Fingerprint application found for + * `secret_api_key_not_found` - No Fingerprint workspace found for specified secret API key. * `public_api_key_required` - public API key in header is missing or empty. - * `public_api_key_not_found` - No Fingerprint application found for + * `public_api_key_not_found` - No Fingerprint workspace found for specified public API key. - * `subscription_not_active` - Fingerprint application is not active. + * `subscription_not_active` - Fingerprint workspace is not active. - * `wrong_region` - Server and application region differ. + * `wrong_region` - Server and workspace region differ. * `feature_not_enabled` - This feature (for example, Delete API) is not - enabled for your application. + enabled for your workspace. * `request_not_found` - The specified event ID was not found. It never existed, expired, or it has been deleted. @@ -1784,9 +2279,13 @@ components: * `payload_too_large` - The request payload is too large and cannot be processed. + + * `service_unavailable` - The service was unable to process the request. + + * `ruleset_not_found` - The specified ruleset was not found. It never + existed or it has been deleted. Error: type: object - additionalProperties: false required: - code - message @@ -1797,7 +2296,6 @@ components: type: string ErrorResponse: type: object - additionalProperties: false required: - error properties: @@ -1824,7 +2322,6 @@ components: description: >- Contains a list of all identification events matching the specified search criteria. - additionalProperties: false required: - events properties: @@ -1844,3 +2341,52 @@ components: This value represents the total number of events matching the search query, up to the limit provided in the `total_hits` query parameter. Only present if the `total_hits` query parameter was provided. + SearchEventsBot: + type: string + enum: + - all + - good + - bad + - none + description: > + Filter events by the Bot Detection result, specifically: + `all` - events where any kind of bot was detected. + `good` - events where a good bot was detected. + `bad` - events where a bad bot was detected. + `none` - events where no bot was detected. + > Note: When using this parameter, only events with the `bot` property + set to a valid value are returned. Events without a `bot` Smart Signal + result are left out of the response. + SearchEventsVpnConfidence: + type: string + enum: + - high + - medium + - low + description: > + Filter events by VPN Detection result confidence level. + + `high` - events with high VPN Detection confidence. + + `medium` - events with medium VPN Detection confidence. + + `low` - events with low VPN Detection confidence. + + > Note: When using this parameter, only events with the `vpn.confidence` + property set to a valid value are returned. Events without a `vpn` Smart + Signal result are left out of the response. + SearchEventsSdkPlatform: + type: string + enum: + - js + - android + - ios + description: > + Filter events by the SDK Platform associated with the identification + event (`sdk.platform` property) . + + `js` - Javascript agent (Web). + + `ios` - Apple iOS based devices. + + `android` - Android based devices. diff --git a/src/generatedApiTypes.ts b/src/generatedApiTypes.ts index f84f9ac3..d5953d43 100644 --- a/src/generatedApiTypes.ts +++ b/src/generatedApiTypes.ts @@ -54,12 +54,16 @@ export interface paths { * * If you don't provide `start` or `end` parameters, the default search range is the **last 7 days**. * - * ### Filtering events with the`suspect` flag + * ### Filtering events with the `suspect` flag * * The `/v4/events` endpoint unlocks a powerful method for fraud protection analytics. The `suspect` flag is exposed in all events where it was previously set by the update API. * * You can also apply the `suspect` query parameter as a filter to find all potentially fraudulent activity that you previously marked as `suspect`. This helps identify patterns of fraudulent behavior. * + * ### Environment scoping + * + * If you use a secret key that is scoped to an environment, you will only get events associated with the same environment. With a workspace-scoped environment, you will get events from all environments. + * * Smart Signals not activated for your workspace or are not included in the response. * */ @@ -116,25 +120,7 @@ export interface paths { trace?: never } } -export interface webhooks { - event: { - parameters: { - query?: never - header?: never - path?: never - cookie?: never - } - get?: never - put?: never - /** Webhook */ - post: operations['postEventWebhook'] - delete?: never - options?: never - head?: never - patch?: never - trace?: never - } -} +export type webhooks = Record export interface components { schemas: { /** @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` @@ -179,12 +165,6 @@ export interface components { /** @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 @@ -252,6 +232,9 @@ export interface components { /** @description User Agent of the client, for example: `Mozilla/5.0 (Windows NT 6.1; Win64; x64) ....` * */ UserAgent: string + /** @description Client Referrer field corresponds to the `document.referrer` field gathered during an identification request. The value is an empty string if the user navigated to the page directly (not through a link, but, for example, by using a bookmark) For example: `https://example.com/blog/my-article` + * */ + ClientReferrer: string BrowserDetails: { browser_name: string browser_major_version: string @@ -284,16 +267,42 @@ export interface components { } /** * @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 + * * `good` - good bot detected, such as Google bot, Baidu Spider, AlexaBot and so on + * * `not_detected` - the visitor is not a bot * * @enum {string} */ - BotResult: 'not_detected' | 'good' | 'bad' + BotResult: 'bad' | 'good' | 'not_detected' /** @description Additional classification of the bot type if detected. * */ BotType: string + /** @description Extended bot information. */ + BotInfo: { + /** @description The type and purpose of the bot. */ + category: string + /** @description The organization or company operating the bot. */ + provider: string + /** @description The URL of the bot provider's website. */ + provider_url?: string + /** @description The specific name or identifier of the bot. */ + name: string + /** + * @description The verification status of the bot's identity: + * * `verified` - well-known bot with publicly verifiable identity, directed by the bot provider. + * * `signed` - bot that signs its platform via Web Bot Auth, directed by the bot provider’s customers. + * * `spoofed` - bot that claims a public identity but fails verification. + * * `unknown` - bot that does not publish a verifiable identity. + * + * @enum {string} + */ + identity: 'verified' | 'signed' | 'spoofed' | 'unknown' + /** + * @description Confidence level of the bot identification. + * @enum {string} + */ + confidence: 'low' | 'medium' | 'high' + } /** @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 @@ -351,6 +360,7 @@ export interface components { asn?: string asn_name?: string asn_network?: string + asn_type?: string datacenter_result?: boolean datacenter_name?: string } @@ -361,6 +371,7 @@ export interface components { asn?: string asn_name?: string asn_network?: string + asn_type?: string datacenter_result?: boolean datacenter_name?: string } @@ -393,6 +404,10 @@ export interface components { * */ last_seen_at?: number + /** @description String representing the last proxy service provider detected when this + * IP was synced. An IP can be shared by multiple service providers. + * */ + provider?: string } /** @description `true` if we detected incognito mode used in the browser, `false` otherwise. * */ @@ -417,6 +432,74 @@ export interface components { * * `false` - No Root Management Apps detected or the client isn't Android. * */ RootApps: boolean + /** @description The ID of the evaluated ruleset. */ + RulesetId: string + /** @description The ID of the rule that matched the identification event. */ + RuleId: string + /** @description The expression of the rule that matched the identification event. */ + RuleExpression: string + /** + * @description Describes the action to take with the request. + * @enum {string} + */ + RuleActionType: 'allow' | 'block' + RuleActionHeaderField: { + /** @description The header field name. */ + name: string + /** @description The value of the header field. */ + value: string + } + /** @description The set of header modifications to apply, in the following order: remove, set, append. */ + RequestHeaderModifications: { + /** @description The list of headers to remove. */ + remove?: string[] + /** @description The list of headers to set, overwriting any existing headers with the same name. */ + set?: components['schemas']['RuleActionHeaderField'][] + /** @description The list of headers to append. */ + append?: components['schemas']['RuleActionHeaderField'][] + } + /** @description Informs the client that the request should be forwarded to the origin with optional request header modifications. */ + EventRuleActionAllow: { + /** @description The ID of the evaluated ruleset. */ + ruleset_id: components['schemas']['RulesetId'] + /** @description The ID of the rule that matched the identification event. */ + rule_id?: components['schemas']['RuleId'] + /** @description The expression of the rule that matched the identification event. */ + rule_expression?: components['schemas']['RuleExpression'] + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: 'allow' + /** @description The set of header modifications to apply, in the following order: remove, set, append. */ + request_header_modifications?: components['schemas']['RequestHeaderModifications'] + } + /** @description A valid HTTP status code. */ + StatusCode: number + /** @description The response body to send to the client. */ + RuleActionBody: string + /** @description Informs the client the request should be blocked using the response described by this rule action. */ + EventRuleActionBlock: { + /** @description The ID of the evaluated ruleset. */ + ruleset_id: components['schemas']['RulesetId'] + /** @description The ID of the rule that matched the identification event. */ + rule_id?: components['schemas']['RuleId'] + /** @description The expression of the rule that matched the identification event. */ + rule_expression?: components['schemas']['RuleExpression'] + /** + * @description discriminator enum property added by openapi-typescript + * @enum {string} + */ + type: 'block' + /** @description A valid HTTP status code. */ + status_code?: components['schemas']['StatusCode'] + /** @description A list of headers to send. */ + headers?: components['schemas']['RuleActionHeaderField'][] + /** @description The response body to send to the client. */ + body?: components['schemas']['RuleActionBody'] + } + /** @description Describes the action the client should take, according to the rule in the ruleset that matched the event. When getting an event by event ID, the rule_action will only be included when the ruleset_id query parameter is specified. */ + EventRuleAction: components['schemas']['EventRuleActionAllow'] | components['schemas']['EventRuleActionBlock'] /** @description Suspect Score is an easy way to integrate Smart Signals into your fraud protection work flow. It is a weighted representation of all Smart Signals present in the payload that helps identify suspicious activity. The value range is [0; S] where S is sum of all Smart Signals weights. See more details here: https://dev.fingerprint.com/docs/suspect-score * */ SuspectScore: number @@ -528,13 +611,218 @@ export interface components { * */ relay?: boolean } + /** @description Flag indicating if the request came from a high-activity visitor. */ + HighActivity: boolean + /** @description Baseline measurement of canonical fonts rendered on the device. Numeric width metrics, in CSS pixels, for the canonical fonts collected by the agent. + * */ + FontPreferences: { + /** Format: double */ + default?: number + /** Format: double */ + serif?: number + /** Format: double */ + sans?: number + /** Format: double */ + mono?: number + /** Format: double */ + apple?: number + /** Format: double */ + min?: number + /** Format: double */ + system?: number + } + /** @description Bounding box metrics describing how the emoji glyph renders. */ + Emoji: { + /** @description Font family reported by the browser when drawing the emoji. */ + font?: string + /** Format: double */ + width?: number + /** Format: double */ + height?: number + /** Format: double */ + top?: number + /** Format: double */ + bottom?: number + /** Format: double */ + left?: number + /** Format: double */ + right?: number + /** Format: double */ + x?: number + /** Format: double */ + y?: number + } + /** + * @description List of fonts detected on the device. + * @example [ + * "Arial Unicode MS", + * "Gill Sans", + * "Helvetica Neue", + * "Menlo" + * ] + */ + Fonts: string[] + /** + * Format: int32 + * @description Rounded amount of RAM (in gigabytes) reported by the browser. + * @example 8 + */ + DeviceMemory: number + /** @description Timezone identifier detected on the client. */ + Timezone: string + /** @description Canvas fingerprint containing winding flag plus geometry/text hashes. */ + Canvas: { + winding?: boolean + /** @description Hash of geometry rendering output or `unsupported` markers. */ + geometry?: string + /** @description Hash of text rendering output or `unsupported` markers. */ + text?: string + } + /** @description Navigator languages reported by the agent including fallbacks. Each inner array represents ordered language preferences reported by different APIs. + * */ + Languages: string[][] + /** @description Hashes of WebGL context attributes and extension support. */ + WebGlExtensions: { + context_attributes?: string + parameters?: string + shader_precisions?: string + extensions?: string + extension_parameters?: string + unsupported_extensions?: string[] + } + /** @description Render and vendor strings reported by the WebGL context. */ + WebGlBasics: { + version?: string + vendor?: string + vendor_unmasked?: string + renderer?: string + renderer_unmasked?: string + shading_language_version?: string + } + /** @description Current screen resolution. */ + ScreenResolution: number[] + /** @description Browser-reported touch capabilities. */ + TouchSupport: { + touch_event?: boolean + touch_start?: boolean + /** Format: int64 */ + max_touch_points?: number + } + /** @description Navigator `oscpu` string. */ + Oscpu: string + /** + * Format: int32 + * @description Integer representing the CPU architecture exposed by the browser. + */ + Architecture: number + /** @description Whether the cookies are enabled in the browser. */ + CookiesEnabled: boolean + /** + * Format: int32 + * @description Number of logical CPU cores reported by the browser. + */ + HardwareConcurrency: number + /** @description Locale derived from the Intl.DateTimeFormat API. Negative values indicate known error states. The negative statuses can be: - "-1": A permanent status for browsers that don't support Intl API. - "-2": A permanent status for browsers that don't supportDateTimeFormat constructor. - "-3": A permanent status for browsers in which DateTimeFormat locale is undefined or null. + * */ + DateTimeLocale: string + /** @description Navigator vendor string. */ + Vendor: string + /** + * Format: int32 + * @description Screen color depth in bits. + */ + ColorDepth: number + /** @description Navigator platform string. */ + Platform: string + /** @description Whether sessionStorage is available. */ + SessionStorage: boolean + /** @description Whether localStorage is available. */ + LocalStorage: boolean + /** + * Format: double + * @description AudioContext fingerprint or negative status when unavailable. The negative statuses can be: - -1: A permanent status for those browsers which are known to always suspend audio context - -2: A permanent status for browsers that don't support the signal - -3: A temporary status that means that an unexpected timeout has happened + * + */ + Audio: number + /** @description Browser plugins reported by `navigator.plugins`. */ + Plugins: { + name: string + description?: string + mimeTypes?: { + type?: string + suffixes?: string + description?: string + }[] + }[] + /** @description Whether IndexedDB is available. */ + IndexedDb: boolean + /** @description Hash of Math APIs used for entropy collection. */ + Math: string + /** @description A curated subset of raw browser/device attributes that the API surface exposes. Each property contains a value or object with the data for the collected signal. + * */ + RawDeviceAttributes: { + /** @description Baseline measurement of canonical fonts rendered on the device. Numeric width metrics, in CSS pixels, for the canonical fonts collected by the agent. + * */ + font_preferences?: components['schemas']['FontPreferences'] + /** @description Bounding box metrics describing how the emoji glyph renders. */ + emoji?: components['schemas']['Emoji'] + /** @description List of fonts detected on the device. */ + fonts?: components['schemas']['Fonts'] + /** @description Rounded amount of RAM (in gigabytes) reported by the browser. */ + device_memory?: components['schemas']['DeviceMemory'] + /** @description Timezone identifier detected on the client. */ + timezone?: components['schemas']['Timezone'] + /** @description Canvas fingerprint containing winding flag plus geometry/text hashes. */ + canvas?: components['schemas']['Canvas'] + /** @description Navigator languages reported by the agent including fallbacks. Each inner array represents ordered language preferences reported by different APIs. + * */ + languages?: components['schemas']['Languages'] + /** @description Hashes of WebGL context attributes and extension support. */ + webgl_extensions?: components['schemas']['WebGlExtensions'] + /** @description Render and vendor strings reported by the WebGL context. */ + webgl_basics?: components['schemas']['WebGlBasics'] + /** @description Current screen resolution. */ + screen_resolution?: components['schemas']['ScreenResolution'] + /** @description Browser-reported touch capabilities. */ + touch_support?: components['schemas']['TouchSupport'] + /** @description Navigator `oscpu` string. */ + oscpu?: components['schemas']['Oscpu'] + /** @description Integer representing the CPU architecture exposed by the browser. */ + architecture?: components['schemas']['Architecture'] + /** @description Whether the cookies are enabled in the browser. */ + cookies_enabled?: components['schemas']['CookiesEnabled'] + /** @description Number of logical CPU cores reported by the browser. */ + hardware_concurrency?: components['schemas']['HardwareConcurrency'] + /** @description Locale derived from the Intl.DateTimeFormat API. Negative values indicate known error states. The negative statuses can be: - "-1": A permanent status for browsers that don't support Intl API. - "-2": A permanent status for browsers that don't supportDateTimeFormat constructor. - "-3": A permanent status for browsers in which DateTimeFormat locale is undefined or null. + * */ + date_time_locale?: components['schemas']['DateTimeLocale'] + /** @description Navigator vendor string. */ + vendor?: components['schemas']['Vendor'] + /** @description Screen color depth in bits. */ + color_depth?: components['schemas']['ColorDepth'] + /** @description Navigator platform string. */ + platform?: components['schemas']['Platform'] + /** @description Whether sessionStorage is available. */ + session_storage?: components['schemas']['SessionStorage'] + /** @description Whether localStorage is available. */ + local_storage?: components['schemas']['LocalStorage'] + /** @description AudioContext fingerprint or negative status when unavailable. The negative statuses can be: - -1: A permanent status for those browsers which are known to always suspend audio context - -2: A permanent status for browsers that don't support the signal - -3: A temporary status that means that an unexpected timeout has happened + * */ + audio?: components['schemas']['Audio'] + /** @description Browser plugins reported by `navigator.plugins`. */ + plugins?: components['schemas']['Plugins'] + /** @description Whether IndexedDB is available. */ + indexed_db?: components['schemas']['IndexedDb'] + /** @description Hash of Math APIs used for entropy collection. */ + math?: components['schemas']['Math'] + } /** @description Contains results from Fingerprint Identification and all active Smart Signals. */ Event: { /** @description Unique identifier of the user's request. The first portion of the event_id is a unix epoch milliseconds timestamp For example: `1758130560902.8tRtrH` * */ - event_id?: components['schemas']['EventId'] + event_id: components['schemas']['EventId'] /** @description Timestamp of the event with millisecond precision in Unix time. */ - timestamp?: components['schemas']['Timestamp'] + 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` @@ -566,19 +854,24 @@ export interface components { /** @description User Agent of the client, for example: `Mozilla/5.0 (Windows NT 6.1; Win64; x64) ....` * */ user_agent?: components['schemas']['UserAgent'] + /** @description Client Referrer field corresponds to the `document.referrer` field gathered during an identification request. The value is an empty string if the user navigated to the page directly (not through a link, but, for example, by using a bookmark) For example: `https://example.com/blog/my-article` + * */ + client_referrer?: components['schemas']['ClientReferrer'] browser_details?: components['schemas']['BrowserDetails'] /** @description Proximity ID represents a fixed geographical zone in a discrete global grid within which the device is observed. * */ proximity?: components['schemas']['Proximity'] /** @description Bot detection result: - * * `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 + * * `good` - good bot detected, such as Google bot, Baidu Spider, AlexaBot and so on + * * `not_detected` - the visitor is not a bot * */ bot?: components['schemas']['BotResult'] /** @description Additional classification of the bot type if detected. * */ bot_type?: components['schemas']['BotType'] + /** @description Extended bot information. */ + bot_info?: components['schemas']['BotInfo'] /** @description Android specific cloned application detection. There are 2 values: * `true` - Presence of app cloners work detected (e.g. fully cloned application found or launch of it inside of a not main working profile detected). * `false` - No signs of cloned application detected or the client is not Android. * */ cloned_app?: components['schemas']['ClonedApp'] @@ -632,6 +925,8 @@ export interface components { * * `false` - No Root Management Apps detected or the client isn't Android. * */ root_apps?: components['schemas']['RootApps'] + /** @description Describes the action the client should take, according to the rule in the ruleset that matched the event. When getting an event by event ID, the rule_action will only be included when the ruleset_id query parameter is specified. */ + rule_action?: components['schemas']['EventRuleAction'] /** @description Suspect Score is an easy way to integrate Smart Signals into your fraud protection work flow. It is a weighted representation of all Smart Signals present in the payload that helps identify suspicious activity. The value range is [0; S] where S is sum of all Smart Signals weights. See more details here: https://dev.fingerprint.com/docs/suspect-score * */ suspect_score?: components['schemas']['SuspectScore'] @@ -676,18 +971,23 @@ export interface components { * */ vpn_origin_country?: components['schemas']['VpnOriginCountry'] vpn_methods?: components['schemas']['VpnMethods'] + /** @description Flag indicating if the request came from a high-activity visitor. */ + high_activity_device?: components['schemas']['HighActivity'] + /** @description A curated subset of raw browser/device attributes that the API surface exposes. Each property contains a value or object with the data for the collected signal. + * */ + raw_device_attributes?: components['schemas']['RawDeviceAttributes'] } /** * @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. + * * `secret_api_key_not_found` - No Fingerprint workspace found for specified secret API key. * * `public_api_key_required` - public API key in header is missing or empty. - * * `public_api_key_not_found` - No Fingerprint 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. + * * `public_api_key_not_found` - No Fingerprint workspace found for specified public API key. + * * `subscription_not_active` - Fingerprint workspace is not active. + * * `wrong_region` - Server and workspace region differ. + * * `feature_not_enabled` - This feature (for example, Delete API) is not enabled for your workspace. * * `request_not_found` - The specified event ID was not found. It never existed, expired, or it has been deleted. * * `visitor_not_found` - The specified visitor ID was not found. It never existed or it may have already been deleted. * * `too_many_requests` - The limit on secret API key requests per second has been exceeded. @@ -700,6 +1000,8 @@ export interface components { * * `event_not_found` - The specified event ID was not found. It never existed, expired, or it has been deleted. * * `missing_module` - The request is invalid because it is missing a required module. * * `payload_too_large` - The request payload is too large and cannot be processed. + * * `service_unavailable` - The service was unable to process the request. + * * `ruleset_not_found` - The specified ruleset was not found. It never existed or it has been deleted. * * @enum {string} */ @@ -720,17 +1022,19 @@ export interface components { | 'event_not_found' | 'missing_module' | 'payload_too_large' + | 'service_unavailable' + | 'ruleset_not_found' Error: { /** @description Error code: * * `request_cannot_be_parsed` - The query parameters or JSON payload contains some errors * that prevented us from parsing it (wrong type/surpassed limits). * * `secret_api_key_required` - secret API key in header is missing or empty. - * * `secret_api_key_not_found` - No Fingerprint application found for specified secret API key. + * * `secret_api_key_not_found` - No Fingerprint workspace found for specified secret API key. * * `public_api_key_required` - public API key in header is missing or empty. - * * `public_api_key_not_found` - No Fingerprint 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. + * * `public_api_key_not_found` - No Fingerprint workspace found for specified public API key. + * * `subscription_not_active` - Fingerprint workspace is not active. + * * `wrong_region` - Server and workspace region differ. + * * `feature_not_enabled` - This feature (for example, Delete API) is not enabled for your workspace. * * `request_not_found` - The specified event ID was not found. It never existed, expired, or it has been deleted. * * `visitor_not_found` - The specified visitor ID was not found. It never existed or it may have already been deleted. * * `too_many_requests` - The limit on secret API key requests per second has been exceeded. @@ -743,6 +1047,8 @@ export interface components { * * `event_not_found` - The specified event ID was not found. It never existed, expired, or it has been deleted. * * `missing_module` - The request is invalid because it is missing a required module. * * `payload_too_large` - The request payload is too large and cannot be processed. + * * `service_unavailable` - The service was unable to process the request. + * * `ruleset_not_found` - The specified ruleset was not found. It never existed or it has been deleted. * */ code: components['schemas']['ErrorCode'] message: string @@ -771,6 +1077,36 @@ export interface components { */ total_hits?: number } + /** + * @description Filter events by the Bot Detection result, specifically: + * `all` - events where any kind of bot was detected. + * `good` - events where a good bot was detected. + * `bad` - events where a bad bot was detected. + * `none` - events where no bot was detected. + * > Note: When using this parameter, only events with the `bot` property set to a valid value are returned. Events without a `bot` Smart Signal result are left out of the response. + * + * @enum {string} + */ + SearchEventsBot: 'all' | 'good' | 'bad' | 'none' + /** + * @description Filter events by VPN Detection result confidence level. + * `high` - events with high VPN Detection confidence. + * `medium` - events with medium VPN Detection confidence. + * `low` - events with low VPN Detection confidence. + * > Note: When using this parameter, only events with the `vpn.confidence` property set to a valid value are returned. Events without a `vpn` Smart Signal result are left out of the response. + * + * @enum {string} + */ + SearchEventsVpnConfidence: 'high' | 'medium' | 'low' + /** + * @description Filter events by the SDK Platform associated with the identification event (`sdk.platform` property) . + * `js` - Javascript agent (Web). + * `ios` - Apple iOS based devices. + * `android` - Android based devices. + * + * @enum {string} + */ + SearchEventsSdkPlatform: 'js' | 'android' | 'ios' } responses: never parameters: never @@ -782,7 +1118,12 @@ export type $defs = Record export interface operations { getEvent: { parameters: { - query?: never + query?: { + /** @description The ID of the ruleset to evaluate against the event, producing the action to take for this event. + * The resulting action is returned in the `rule_action` attribute of the response. + * */ + ruleset_id?: string + } header?: never path: { /** @description The unique [identifier](https://dev.fingerprint.com/reference/get-function#requestid) of each identification request (`requestId` can be used in its place). */ @@ -819,7 +1160,7 @@ export interface operations { 'application/json': components['schemas']['ErrorResponse'] } } - /** @description Not found. The event Id cannot be found in this application's data. */ + /** @description Not found. The event Id cannot be found in this workspace's data. */ 404: { headers: { [name: string]: unknown @@ -828,7 +1169,16 @@ export interface operations { 'application/json': components['schemas']['ErrorResponse'] } } - /** @description Application error. */ + /** @description Too Many Requests. The request is throttled. */ + 429: { + headers: { + [name: string]: unknown + } + content: { + 'application/json': components['schemas']['ErrorResponse'] + } + } + /** @description Workspace error. */ 500: { headers: { [name: string]: unknown @@ -880,7 +1230,7 @@ export interface operations { 'application/json': components['schemas']['ErrorResponse'] } } - /** @description Not found. The event Id cannot be found in this application's data. */ + /** @description Not found. The event Id cannot be found in this workspace's data. */ 404: { headers: { [name: string]: unknown @@ -908,7 +1258,7 @@ export interface operations { limit?: number /** @description Use `pagination_key` 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: + * When more results are available (e.g., you requested up to 100 results for your query using `limit`, but there are more than 100 events total matching your request), the `pagination_key` field is added to the response. The pagination key is an arbitrary string that should not be interpreted in any way and should be passed as-is. In the following request, use that value in the `pagination_key` parameter to get the next page of results: * * 1. First request, returning most recent 200 events: `GET api-base-url/events?limit=100` * 2. Use `response.pagination_key` to get the next page of results: `GET api-base-url/events?limit=100&pagination_key=1740815825085` @@ -923,13 +1273,17 @@ 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 `botd.bot` property set to a valid value are returned. Events without a `botd` Smart Signal result are left out of the response. + * > Note: When using this parameter, only events with the `bot` property set to a valid value are returned. Events without a `bot` Smart Signal result are left out of the response. * */ - bot?: 'all' | 'good' | 'bad' | 'none' + bot?: components['schemas']['SearchEventsBot'] /** @description Filter events by IP address or IP range (if CIDR notation is used). If CIDR notation is not used, a /32 for IPv4 or /128 for IPv6 is assumed. * Examples of range based queries: 10.0.0.0/24, 192.168.0.1/32 * */ ip_address?: string + /** @description Filter events by the ASN associated with the event's IP address. + * This corresponds to the `ip_info.(v4|v6).asn` property in the response. + * */ + asn?: string /** @description Filter events by your custom identifier. * * You can use [linked Ids](https://dev.fingerprint.com/reference/get-function#linkedid) to associate identification requests with your own identifier, for example, session Id, purchase Id, or transaction Id. You can then use this `linked_id` parameter to retrieve all events associated with your custom identifier. @@ -938,7 +1292,13 @@ export interface operations { /** @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) + /** @description Filter events by the Bundle ID (iOS) associated with the event. + * */ + bundle_id?: string + /** @description Filter events by the Package Name (Android) associated with the event. + * */ + package_name?: string + /** @description Filter events by the origin field of the event. This is applicable to web events only (e.g., https://example.com) * */ origin?: string /** @description Filter events with a timestamp greater than the start time, in Unix time (milliseconds). @@ -1008,7 +1368,7 @@ export interface operations { * `low` - events with low VPN Detection confidence. * > Note: When using this parameter, only events with the `vpn.confidence` property set to a valid value are returned. Events without a `vpn` Smart Signal result are left out of the response. * */ - vpn_confidence?: 'high,' | 'medium' | 'low' + vpn_confidence?: components['schemas']['SearchEventsVpnConfidence'] /** @description Filter events with Suspect Score result above a provided minimum threshold. * > Note: When using this parameter, only events where the `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. * */ @@ -1037,13 +1397,21 @@ export interface operations { * `ios` - Apple iOS based devices. * `android` - Android based devices. * */ - sdk_platform?: 'js' | 'android' | 'ios' + sdk_platform?: components['schemas']['SearchEventsSdkPlatform'] /** @description Filter for events by providing one or more environment IDs (`environment_id` property). * */ environment?: string[] + /** @description Filter events by the most precise Proximity ID provided by default. + * > Note: When using this parameter, only events with the `proximity.id` property matching the provided ID are returned. Events without a `proximity` result are left out of the response. + * */ + proximity_id?: string /** @description When set, the response will include a `total_hits` property with a count of total query matches across all pages, up to the specified limit. * */ total_hits?: number + /** @description Filter events by Tor Node detection result. + * > Note: When using this parameter, only events with the `tor_node` property set to `true` or `false` are returned. Events without a `tor_node` detection result are left out of the response. + * */ + tor_node?: boolean } header?: never path?: never @@ -1078,7 +1446,7 @@ export interface operations { 'application/json': components['schemas']['ErrorResponse'] } } - /** @description Application error. */ + /** @description Workspace error. */ 500: { headers: { [name: string]: unknown @@ -1126,7 +1494,7 @@ export interface operations { 'application/json': components['schemas']['ErrorResponse'] } } - /** @description Not found. The visitor ID cannot be found in this application's data. */ + /** @description Not found. The visitor ID cannot be found in this workspace's data. */ 404: { headers: { [name: string]: unknown @@ -1146,28 +1514,4 @@ export interface operations { } } } - postEventWebhook: { - parameters: { - query?: never - header?: never - path?: never - cookie?: 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 Return a 200 status to indicate that the data was received successfully */ - 200: { - headers: { - [name: string]: unknown - } - content?: never - } - } - } } From 67eed4c61c785a0733e686dde210ad7d8f9a2b8a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eray=20Ayd=C4=B1n?= Date: Mon, 23 Feb 2026 11:33:49 +0300 Subject: [PATCH 38/71] chore: update repository URLs to new repo name Rename all references from `fingerprintjs/fingerprintjs-pro-server-api-node-sdk` to `fingerprintjs/node-sdk`. --- .changeset/config.json | 2 +- package.json | 2 +- readme.md | 18 +++++++++--------- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.changeset/config.json b/.changeset/config.json index 339730c2..70dd986c 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -3,7 +3,7 @@ "changelog": [ "@fingerprintjs/changesets-changelog-format", { - "repo": "fingerprintjs/fingerprintjs-pro-server-api-node-sdk" + "repo": "fingerprintjs/node-sdk" } ], "commit": false, diff --git a/package.json b/package.json index ed648233..38ab5e73 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ }, "repository": { "type": "git", - "url": "https://github.com/fingerprintjs/fingerprintjs-pro-server-api-node-sdk" + "url": "https://github.com/fingerprintjs/node-sdk" }, "engines": { "node": ">=18.17.0" diff --git a/readme.md b/readme.md index 2ca790ed..069d2706 100644 --- a/readme.md +++ b/readme.md @@ -8,8 +8,8 @@

- Build status - coverage + Build status + coverage Current NPM version Monthly downloads from NPM Discord server @@ -104,7 +104,7 @@ client }) ``` -See the [Examples](https://github.com/fingerprintjs/fingerprintjs-pro-server-api-node-sdk/tree/main/example) folder for more detailed examples. +See the [Examples](https://github.com/fingerprintjs/node-sdk/tree/main/example) folder for more detailed examples. ### Error handling @@ -155,21 +155,21 @@ const event = eventWebhookBody as unknown as Event Customers on the Enterprise plan can enable [Webhook signatures](https://dev.fingerprint.com/docs/webhooks-security) to cryptographically verify the authenticity of incoming webhooks. This SDK provides a utility method for verifying the HMAC signature of the incoming webhook request. -To learn more, see [example/validateWebhookSignature.mjs](example/validateWebhookSignature.mjs) or read the [API Reference](https://fingerprintjs.github.io/fingerprintjs-pro-server-api-node-sdk/functions/isValidWebhookSignature.html). +To learn more, see [example/validateWebhookSignature.mjs](example/validateWebhookSignature.mjs) or read the [API Reference](https://fingerprintjs.github.io/node-sdk/functions/isValidWebhookSignature.html). ### Sealed results Customers on the Enterprise plan can enable [Sealed results](https://dev.fingerprint.com/docs/sealed-client-results) to receive the full device intelligence result on the client and unseal it on the server. This SDK provides utility methods for decoding sealed results. -To learn more, see [example/unsealResult.mjs](https://github.com/fingerprintjs/fingerprintjs-pro-server-api-node-sdk/tree/main/example/unsealResult.mjs) or the [API Reference](https://fingerprintjs.github.io/fingerprintjs-pro-server-api-node-sdk/functions/unsealEventsResponse.html). +To learn more, see [example/unsealResult.mjs](https://github.com/fingerprintjs/node-sdk/tree/main/example/unsealResult.mjs) or the [API Reference](https://fingerprintjs.github.io/node-sdk/functions/unsealEventsResponse.html). ### Deleting visitor data -Customers on the Enterprise plan can [Delete all data associated with a specific visitor](https://dev.fingerprint.com/reference/deletevisitordata) to comply with privacy regulations. See [example/deleteVisitor.mjs](https://github.com/fingerprintjs/fingerprintjs-pro-server-api-node-sdk/tree/main/example/deleteVisitor.mjs) or the [API Reference](https://fingerprintjs.github.io/fingerprintjs-pro-server-api-node-sdk/classes/FingerprintJsServerApiClient.html#deleteVisitorData). +Customers on the Enterprise plan can [Delete all data associated with a specific visitor](https://dev.fingerprint.com/reference/deletevisitordata) to comply with privacy regulations. See [example/deleteVisitor.mjs](https://github.com/fingerprintjs/node-sdk/tree/main/example/deleteVisitor.mjs) or the [API Reference](https://fingerprintjs.github.io/node-sdk/classes/FingerprintJsServerApiClient.html#deleteVisitorData). ## API Reference -See the full [API reference](https://fingerprintjs.github.io/fingerprintjs-pro-server-api-node-sdk/). +See the full [API reference](https://fingerprintjs.github.io/node-sdk/). ## Semantic versioning @@ -179,8 +179,8 @@ See the full [API reference](https://fingerprintjs.github.io/fingerprintjs-pro-s ## Support and feedback -To report problems, ask questions, or provide feedback, please use [Issues](https://github.com/fingerprintjs/fingerprintjs-pro-server-api-node-sdk/issues). If you need private support, you can email us at [oss-support@fingerprint.com](mailto:oss-support@fingerprint.com). +To report problems, ask questions, or provide feedback, please use [Issues](https://github.com/fingerprintjs/node-sdk/issues). If you need private support, you can email us at [oss-support@fingerprint.com](mailto:oss-support@fingerprint.com). ## License -This project is licensed under the [MIT license](https://github.com/fingerprintjs/fingerprintjs-pro-server-api-node-sdk/tree/main/LICENSE). +This project is licensed under the [MIT license](https://github.com/fingerprintjs/node-sdk/tree/main/LICENSE). From b75c5546b29fec6d42edb48f726857d0691afcba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eray=20Ayd=C4=B1n?= Date: Mon, 23 Feb 2026 12:04:17 +0300 Subject: [PATCH 39/71] chore: remove unnecessary `WebhookOperationIds` Removes unnecessary `WebhookOperationIds` type. --- src/types.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/types.ts b/src/types.ts index 3c7dca46..e9360dce 100644 --- a/src/types.ts +++ b/src/types.ts @@ -59,10 +59,6 @@ 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 @@ -86,5 +82,5 @@ type ApiMethod = ( ) => Promise> export type FingerprintApi = { - [Operation in ClientOperationKeys]: ApiMethod + [Operation in keyof operations]: ApiMethod } From 3ebae43482985bf1100ce9cc499fa5da1caeb45f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eray=20Ayd=C4=B1n?= Date: Mon, 23 Feb 2026 12:50:42 +0300 Subject: [PATCH 40/71] feat: add rulesetId support to getEvent Add optional `rulesetId` parameter to `getEvent` method. Related-Task: INTER-1488 --- .changeset/witty-lobsters-film.md | 5 + example/getEvent.mjs | 12 +- readme.md | 5 + src/serverApiClient.ts | 4 +- src/types.ts | 18 +- sync.sh | 1 + .../getEventTests.spec.ts | 17 + .../events/get_event_ruleset_200.json | 296 ++++++++++++++++++ 8 files changed, 355 insertions(+), 3 deletions(-) create mode 100644 .changeset/witty-lobsters-film.md create mode 100644 tests/mocked-responses-tests/mocked-responses-data/events/get_event_ruleset_200.json diff --git a/.changeset/witty-lobsters-film.md b/.changeset/witty-lobsters-film.md new file mode 100644 index 00000000..841be6b0 --- /dev/null +++ b/.changeset/witty-lobsters-film.md @@ -0,0 +1,5 @@ +--- +'@fingerprint/fingerprint-server-sdk': minor +--- + +add `rulesetId` parameter to the `getEvent` operation diff --git a/example/getEvent.mjs b/example/getEvent.mjs index b27a916a..5eafa49f 100644 --- a/example/getEvent.mjs +++ b/example/getEvent.mjs @@ -4,6 +4,7 @@ config() const apiKey = process.env.API_KEY const eventId = process.env.EVENT_ID +const rulesetId = process.env.RULESET_ID const envRegion = process.env.REGION if (!eventId) { @@ -26,8 +27,17 @@ if (envRegion === 'eu') { const client = new FingerprintJsServerApiClient({ region, apiKey }) try { - const event = await client.getEvent(eventId) + const event = await client.getEvent(eventId, rulesetId) console.log(JSON.stringify(event, null, 2)) + + if (rulesetId && event.rule_action) { + const { type, ruleset_id, rule_id, rule_expression } = event.rule_action + console.log(`Rule action: ${type} (ruleset: ${ruleset_id}, rule: ${rule_id}, expression: ${rule_expression})`) + + if (type === 'block') { + console.log(`Block response: ${event.rule_action.status_code} ${event.rule_action.body}`) + } + } } catch (error) { if (error instanceof RequestError) { console.log(`error ${error.statusCode}: `, error.message) diff --git a/readme.md b/readme.md index 069d2706..2e4c986f 100644 --- a/readme.md +++ b/readme.md @@ -92,6 +92,11 @@ client.getEvent('').then((event) => { console.log(event) }) +// Get an event with a ruleset evaluation +client.getEvent('', '').then((event) => { + console.log(event.rule_action?.type) // 'allow' or 'block' +}) + // Search for identification events client .searchEvents({ diff --git a/src/serverApiClient.ts b/src/serverApiClient.ts index 43eaf086..784dbd42 100644 --- a/src/serverApiClient.ts +++ b/src/serverApiClient.ts @@ -41,6 +41,7 @@ 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 eventId - identifier of the event + * @param rulesetId - optional ruleset ID to evaluate against the event * * @returns {Promise} - promise with event response. For more information, see the [Server API documentation](https://dev.fingerprint.com/reference/getevent). * @@ -58,7 +59,7 @@ export class FingerprintJsServerApiClient implements FingerprintApi { * }) * ``` * */ - public async getEvent(eventId: string): Promise { + public async getEvent(eventId: string, rulesetId?: string): Promise { if (!eventId) { throw new TypeError('eventId is not set') } @@ -68,6 +69,7 @@ export class FingerprintJsServerApiClient implements FingerprintApi { region: this.region, pathParams: [eventId], method: 'get', + queryParams: rulesetId ? { ruleset_id: rulesetId } : undefined, }) } diff --git a/src/types.ts b/src/types.ts index e9360dce..c51ef285 100644 --- a/src/types.ts +++ b/src/types.ts @@ -45,6 +45,8 @@ export type Event = components['schemas']['Event'] export type EventUpdate = components['schemas']['EventUpdate'] +export type EventRuleAction = components['schemas']['EventRuleAction'] + // Extract just the `path` parameters as a tuple of strings type ExtractPathParamStrings = Path extends { parameters: { path: infer P } } ? P extends Record @@ -67,6 +69,20 @@ type ExtractResponse = Path extends { responses: { 200: { content: { 'appl ? R : void +// Utility type to check union +type IsUnion = T extends U ? ([U] extends [T] ? false : true) : never + +// When query params have a single key, flatten it into an optional arg(`getEvent`) else keep as object(`searchEvents`) +// WARN: This type only affects the public FingerprintApi interface (`serverApiClient`). +// For internal request building (`urlUtils`), use `ExtractQueryParams` which preserves the object form. +type QueryParamArgs = [Q] extends [never] + ? [] + : [Exclude] extends [never] + ? [] + : IsUnion> extends false + ? [param?: Exclude[keyof Exclude]] + : [params: Q] + // Extracts args to given API method type ApiMethodArgs = [ // If method has body, extract it as first parameter @@ -74,7 +90,7 @@ type ApiMethodArgs = [ // 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]), + ...QueryParamArgs>, ] type ApiMethod = ( diff --git a/sync.sh b/sync.sh index 0402daea..5bc43919 100755 --- a/sync.sh +++ b/sync.sh @@ -8,6 +8,7 @@ curl -fSL -o ./resources/fingerprint-server-api.yaml \ examplesList=( 'webhook/webhook_event.json' 'events/get_event_200.json' + 'events/get_event_ruleset_200.json' 'events/search/get_event_search_200.json' 'errors/404_event_not_found.json' 'errors/404_visitor_not_found.json' diff --git a/tests/mocked-responses-tests/getEventTests.spec.ts b/tests/mocked-responses-tests/getEventTests.spec.ts index b0ef3ea7..274d1f72 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 getEventRulesetResponse from './mocked-responses-data/events/get_event_ruleset_200.json' import { createResponse } from './utils' jest.spyOn(global, 'fetch') @@ -15,6 +16,7 @@ const mockFetch = fetch as unknown as jest.Mock describe('[Mocked response] Get Event', () => { const apiKey = 'dummy_api_key' const existingEventId = '1626550679751.cVc5Pm' + const rulesetId = 'rs_b1k1blhqpOX3kU' const client = new FingerprintJsServerApiClient({ region: Region.EU, apiKey }) @@ -33,6 +35,21 @@ describe('[Mocked response] Get Event', () => { expect(response).toEqual(getEventResponse) }) + test('with event_id and ruleset_id', async () => { + mockFetch.mockReturnValue(Promise.resolve(createResponse(getEventRulesetResponse))) + + const response = await client.getEvent(existingEventId, rulesetId) + + expect(mockFetch).toHaveBeenCalledWith( + `https://eu.api.fpjs.io/v4/events/${existingEventId}?ruleset_id=${rulesetId}&ii=${encodeURIComponent(getIntegrationInfo())}`, + { + headers: { Authorization: `Bearer ${apiKey}` }, + method: 'GET', + } + ) + expect(response).toEqual(getEventRulesetResponse) + }) + test('403 error', async () => { const errorInfo = { error: { diff --git a/tests/mocked-responses-tests/mocked-responses-data/events/get_event_ruleset_200.json b/tests/mocked-responses-tests/mocked-responses-data/events/get_event_ruleset_200.json new file mode 100644 index 00000000..f931e47f --- /dev/null +++ b/tests/mocked-responses-tests/mocked-responses-data/events/get_event_ruleset_200.json @@ -0,0 +1,296 @@ +{ + "linked_id": "somelinkedId", + "tags": {}, + "timestamp": 1708102555327, + "event_id": "1708102555327.NLOjmg", + "url": "https://www.example.com/login?hope{this{works[!", + "ip_address": "61.127.217.15", + "user_agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64) ....", + "client_referrer": "https://example.com/blog/my-article", + "browser_details": { + "browser_name": "Chrome", + "browser_major_version": "74", + "browser_full_version": "74.0.3729", + "os": "Windows", + "os_version": "7", + "device": "Other" + }, + "identification": { + "visitor_id": "Ibk1527CUFmcnjLwIs4A9", + "confidence": { + "score": 0.97, + "version": "1.1" + }, + "visitor_found": false, + "first_seen_at": 1708102555327, + "last_seen_at": 1708102555327 + }, + "supplementary_id_high_recall": { + "visitor_id": "3HNey93AkBW6CRbxV6xP", + "visitor_found": true, + "confidence": { + "score": 0.97, + "version": "1.1" + }, + "first_seen_at": 1708102555327, + "last_seen_at": 1708102555327 + }, + "proximity": { + "id": "w1aTfd4MCvl", + "precision_radius": 10, + "confidence": 0.95 + }, + "bot": "not_detected", + "root_apps": false, + "emulator": false, + "ip_info": { + "v4": { + "address": "94.142.239.124", + "geolocation": { + "accuracy_radius": 20, + "latitude": 50.05, + "longitude": 14.4, + "postal_code": "150 00", + "timezone": "Europe/Prague", + "city_name": "Prague", + "country_code": "CZ", + "country_name": "Czechia", + "continent_code": "EU", + "continent_name": "Europe", + "subdivisions": [ + { + "iso_code": "10", + "name": "Hlavni mesto Praha" + } + ] + }, + "asn": "7922", + "asn_name": "COMCAST-7922", + "asn_network": "73.136.0.0/13", + "asn_type": "isp", + "datacenter_result": true, + "datacenter_name": "DediPath" + }, + "v6": { + "address": "2001:db8:3333:4444:5555:6666:7777:8888", + "geolocation": { + "accuracy_radius": 5, + "latitude": 49.982, + "longitude": 36.2566, + "postal_code": "10112", + "timezone": "Europe/Berlin", + "city_name": "Berlin", + "country_code": "DE", + "country_name": "Germany", + "continent_code": "EU", + "continent_name": "Europe", + "subdivisions": [ + { + "iso_code": "BE", + "name": "Land Berlin" + } + ] + }, + "asn": "6805", + "asn_name": "Telefonica Germany", + "asn_network": "2a02:3100::/24", + "asn_type": "isp", + "datacenter_result": false, + "datacenter_name": "" + } + }, + "ip_blocklist": { + "email_spam": false, + "attack_source": false, + "tor_node": false + }, + "proxy": true, + "proxy_confidence": "low", + "proxy_details": { + "proxy_type": "residential", + "last_seen_at": 1708102555327, + "provider": "Massive" + }, + "vpn": false, + "vpn_confidence": "high", + "vpn_origin_timezone": "Europe/Berlin", + "vpn_origin_country": "unknown", + "vpn_methods": { + "timezone_mismatch": false, + "public_vpn": false, + "auxiliary_mobile": false, + "os_mismatch": false, + "relay": false + }, + "incognito": false, + "tampering": false, + "tampering_details": { + "anomaly_score": 0.1955, + "anti_detect_browser": false + }, + "cloned_app": false, + "factory_reset_timestamp": 0, + "jailbroken": false, + "frida": false, + "privacy_settings": false, + "virtual_machine": false, + "location_spoofing": false, + "velocity": { + "distinct_ip": { + "5_minutes": 1, + "1_hour": 1, + "24_hours": 1 + }, + "distinct_country": { + "5_minutes": 1, + "1_hour": 2, + "24_hours": 2 + }, + "events": { + "5_minutes": 1, + "1_hour": 5, + "24_hours": 5 + }, + "ip_events": { + "5_minutes": 1, + "1_hour": 5, + "24_hours": 5 + }, + "distinct_ip_by_linked_id": { + "5_minutes": 1, + "1_hour": 5, + "24_hours": 5 + }, + "distinct_visitor_id_by_linked_id": { + "5_minutes": 1, + "1_hour": 5, + "24_hours": 5 + } + }, + "developer_tools": false, + "mitm_attack": false, + "sdk": { + "platform": "js", + "version": "3.11.10", + "integrations": [ + { + "name": "fingerprint-pro-react", + "version": "3.11.10", + "subintegration": { + "name": "preact", + "version": "10.21.0" + } + } + ] + }, + "replayed": false, + "high_activity_device": false, + "raw_device_attributes": { + "math": "5f030fa7d2e5f9f757bfaf81642eb1a6", + "vendor": "Google Inc.", + "plugins": [ + { + "description": "Portable Document Format", + "mimeTypes": [ + { + "suffixes": "pdf", + "type": "application/pdf" + }, + { + "suffixes": "pdf", + "type": "text/pdf" + } + ], + "name": "PDF Viewer" + } + ], + "webgl_extensions": { + "context_attributes": "6b1ed336830d2bc96442a9d76373252a", + "extension_parameters": "86a8abb36f0cb30b5946dec0c761d042", + "extensions": "57233d7b10f89fcd1ff95e3837ccd72d", + "parameters": "ea118c48e308bc4b0677118bbb3019ec", + "shader_precisions": "f223dfbcd580cf142da156d93790eb83", + "unsupported_extensions": [] + }, + "cookies_enabled": true, + "webgl_basics": { + "renderer": "WebKit WebGL", + "renderer_unmasked": "ANGLE (Apple, ANGLE Metal Renderer: Apple M4, Unspecified Version)", + "shading_language_version": "WebGL GLSL ES 1.0 (OpenGL ES GLSL ES 1.0 Chromium)", + "vendor": "WebKit", + "vendor_unmasked": "Google Inc. (Apple)", + "version": "WebGL 1.0 (OpenGL ES 2.0 Chromium)" + }, + "canvas": { + "geometry": "db3c1462576a399a03ae93d0ab9eb5c4", + "text": "70c3d3f7eb4408dc37a6bf8af1c51029", + "winding": true + }, + "hardware_concurrency": 10, + "languages": [ + [ + "en-US" + ] + ], + "color_depth": 24, + "fonts": [ + "Arial Unicode MS", + "Gill Sans", + "Helvetica Neue", + "Menlo" + ], + "indexed_db": true, + "touch_support": { + "max_touch_points": 0, + "touch_event": false, + "touch_start": false + }, + "device_memory": 8, + "oscpu": "Windows NT 6.1; Win64; x64", + "architecture": 127, + "screen_resolution": [ + 1920, + 1080 + ], + "timezone": "America/Sao_Paulo", + "emoji": { + "bottom": 32, + "font": "Times", + "height": 18, + "left": 8, + "right": 1608, + "top": 14, + "width": 1600, + "x": 8, + "y": 14 + }, + "font_preferences": { + "apple": 147.5625, + "default": 147.5625, + "min": 9.234375, + "mono": 133.0625, + "sans": 144.015625, + "serif": 147.5625, + "system": 146.09375 + }, + "platform": "MacIntel", + "local_storage": true, + "session_storage": true, + "date_time_locale": "en-US", + "audio": 124.04347745512496 + }, + "rule_action": { + "type": "block", + "status_code": 403, + "headers": [ + { + "name": "Content-Type", + "value": "application/json" + } + ], + "body": "{\"title\":\"Forbidden\"}", + "ruleset_id": "rs_b1k1blhqpOX3kU", + "rule_id": "r_uE0af8497PFAOD", + "rule_expression": "bot in [\"bad\"] || incognito" + } +} From e03487310f03874fe010917dad63c2554dd62cec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eray=20Ayd=C4=B1n?= Date: Mon, 23 Feb 2026 13:05:09 +0300 Subject: [PATCH 41/71] test: fix linked_id usage in updateEvent tests Fixed `linked_id` usage in the `UpdateEventTests.spec.ts` file. Related-Task: INTER-1488 --- .../mocked-responses-tests/updateEventTests.spec.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/mocked-responses-tests/updateEventTests.spec.ts b/tests/mocked-responses-tests/updateEventTests.spec.ts index ea5f7718..9dc89523 100644 --- a/tests/mocked-responses-tests/updateEventTests.spec.ts +++ b/tests/mocked-responses-tests/updateEventTests.spec.ts @@ -54,7 +54,7 @@ describe('[Mocked response] Update event', () => { mockFetch.mockReturnValue(Promise.resolve(mockResponse)) const body = { - linkedId: 'linked_id', + linked_id: 'linked_id', suspect: true, } await expect(client.updateEvent(body, existingEventId)).rejects.toThrow( @@ -69,7 +69,7 @@ describe('[Mocked response] Update event', () => { mockFetch.mockReturnValue(Promise.resolve(mockResponse)) const body = { - linkedId: 'linked_id', + linked_id: 'linked_id', suspect: true, } await expect(client.updateEvent(body, existingEventId)).rejects.toThrow( @@ -84,7 +84,7 @@ describe('[Mocked response] Update event', () => { mockFetch.mockReturnValue(Promise.resolve(mockResponse)) const body = { - linkedId: 'linked_id', + linked_id: 'linked_id', suspect: true, } await expect(client.updateEvent(body, existingEventId)).rejects.toThrow( @@ -99,7 +99,7 @@ describe('[Mocked response] Update event', () => { mockFetch.mockReturnValue(Promise.resolve(mockResponse)) const body = { - linkedId: 'linked_id', + linked_id: 'linked_id', suspect: true, } await expect(client.updateEvent(body, existingEventId)).rejects.toThrow( @@ -114,7 +114,7 @@ describe('[Mocked response] Update event', () => { mockFetch.mockReturnValue(Promise.resolve(mockResponse)) const body = { - linkedId: 'linked_id', + linked_id: 'linked_id', suspect: true, } await expect(client.updateEvent(body, existingEventId)).rejects.toMatchObject( @@ -139,7 +139,7 @@ describe('[Mocked response] Update event', () => { mockFetch.mockReturnValue(Promise.resolve(mockResponse)) const body = { - linkedId: 'linked_id', + linked_id: 'linked_id', suspect: true, } await expect(client.updateEvent(body, existingEventId)).rejects.toThrow(RequestError) From 4188e2a7c17fb0207b81c7ce5815dbe6421cd0c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eray=20Ayd=C4=B1n?= Date: Mon, 23 Feb 2026 13:09:45 +0300 Subject: [PATCH 42/71] chore: add new keyword to type error throw Add `new` keyword to the `deleteVisitorData` throwing Type Error. 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 784dbd42..39c83572 100644 --- a/src/serverApiClient.ts +++ b/src/serverApiClient.ts @@ -161,7 +161,7 @@ export class FingerprintJsServerApiClient implements FingerprintApi { */ public async deleteVisitorData(visitorId: string): Promise { if (!visitorId) { - throw TypeError('VisitorId is not set') + throw new TypeError('VisitorId is not set') } return this.callApi({ From 3e191a90fe92b509a8e9f2eb0b546cff64b9e936 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eray=20Ayd=C4=B1n?= Date: Mon, 23 Feb 2026 13:12:14 +0300 Subject: [PATCH 43/71] test: fix grammar typos Fix grammar typo issues in test descriptions. Related-Task: INTER-1488 --- tests/unit-tests/errorTests.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit-tests/errorTests.spec.ts b/tests/unit-tests/errorTests.spec.ts index e65979a6..933fbd37 100644 --- a/tests/unit-tests/errorTests.spec.ts +++ b/tests/unit-tests/errorTests.spec.ts @@ -39,7 +39,7 @@ describe('TooManyRequestsError', () => { }, } - it('parses retry-after and include to the error', () => { + it('parses retry-after and includes it in the error', () => { const response = makeResponse('60') const err = new TooManyRequestsError(body, response) expect(err).toBeInstanceOf(TooManyRequestsError) @@ -52,7 +52,7 @@ describe('TooManyRequestsError', () => { expect(err.retryAfter).toBe(0) }) - it('use 0 if the response have invalid value', () => { + it('use 0 if the response has invalid value', () => { const response = makeResponse('not-a-number') const err = new TooManyRequestsError(body, response) expect(err.retryAfter).toBe(0) From 1072171027e7c5100b9e8a6c3a36fd71205276e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eray=20Ayd=C4=B1n?= Date: Mon, 23 Feb 2026 13:25:18 +0300 Subject: [PATCH 44/71] chore: enter to pre-release mode Enter to changeset pre-release mode. Related-Task: INTER-1488 --- .changeset/pre.json | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 .changeset/pre.json diff --git a/.changeset/pre.json b/.changeset/pre.json new file mode 100644 index 00000000..2f9c0c51 --- /dev/null +++ b/.changeset/pre.json @@ -0,0 +1,10 @@ +{ + "mode": "pre", + "tag": "test", + "initialVersions": { + "@fingerprint/fingerprint-server-sdk": "6.10.0", + "fingerprint-server-sdk-example": "1.0.0", + "fingerprint-server-sdk-smoke-tests": "2.0.0" + }, + "changesets": [] +} From 68869d73a9538b623c7415ede303b49080cdb5cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eray=20Ayd=C4=B1n?= Date: Mon, 23 Feb 2026 13:46:32 +0300 Subject: [PATCH 45/71] chore: remove duplicated import Remove duplicated `unsealError` import in the `index.ts` file. Related-Task: INTER-1488 --- src/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index c9ce0fa7..7247a1e5 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,5 +5,4 @@ export * from './sealedResults' export * from './errors/unsealError' export * from './webhook' export * from './errors/apiErrors' -export * from './errors/unsealError' export * from './errors/getRetryAfter' From a034171c691eeba7b4c5d7f45e1e4329bb8a31fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eray=20Ayd=C4=B1n?= Date: Mon, 23 Feb 2026 13:47:48 +0300 Subject: [PATCH 46/71] chore: fix inconsistent error message Fix inconsistent error message casing in the `deleteVisitorData` function. Related-Task: INTER-1488 --- src/serverApiClient.ts | 2 +- tests/unit-tests/serverApiClientTests.spec.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/serverApiClient.ts b/src/serverApiClient.ts index 39c83572..f5268cb8 100644 --- a/src/serverApiClient.ts +++ b/src/serverApiClient.ts @@ -161,7 +161,7 @@ export class FingerprintJsServerApiClient implements FingerprintApi { */ public async deleteVisitorData(visitorId: string): Promise { if (!visitorId) { - throw new TypeError('VisitorId is not set') + throw new TypeError('visitorId is not set') } return this.callApi({ diff --git a/tests/unit-tests/serverApiClientTests.spec.ts b/tests/unit-tests/serverApiClientTests.spec.ts index cfbe40f8..c98d2efa 100644 --- a/tests/unit-tests/serverApiClientTests.spec.ts +++ b/tests/unit-tests/serverApiClientTests.spec.ts @@ -95,7 +95,7 @@ describe('ServerApiClient', () => { }) await expect(client.deleteVisitorData(undefined as unknown as string)).rejects.toThrow( - new TypeError('VisitorId is not set') + new TypeError('visitorId is not set') ) }) From 45c2be5b100537f9d6767ce4e1fbaf52d437de05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eray=20Ayd=C4=B1n?= Date: Tue, 24 Feb 2026 20:34:27 +0300 Subject: [PATCH 47/71] refactor: move region parameter to inside callApi Move `region` parameter passing to inside of the `callApi`. 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 f5268cb8..d4ad6eda 100644 --- a/src/serverApiClient.ts +++ b/src/serverApiClient.ts @@ -66,7 +66,6 @@ export class FingerprintJsServerApiClient implements FingerprintApi { return this.callApi({ path: '/events/{event_id}', - region: this.region, pathParams: [eventId], method: 'get', queryParams: rulesetId ? { ruleset_id: rulesetId } : undefined, @@ -122,7 +121,6 @@ export class FingerprintJsServerApiClient implements FingerprintApi { return this.callApi({ path: '/events/{event_id}', - region: this.region, pathParams: [eventId], method: 'patch', body: JSON.stringify(body), @@ -166,7 +164,6 @@ export class FingerprintJsServerApiClient implements FingerprintApi { return this.callApi({ path: '/visitors/{visitor_id}', - region: this.region, pathParams: [visitorId], method: 'delete', }) @@ -241,7 +238,10 @@ export class FingerprintJsServerApiClient implements FingerprintApi { private async callApi>( options: GetRequestPathOptions & { headers?: Record; body?: BodyInit } ): Promise> { - const url = getRequestPath(options) + const url = getRequestPath({ + ...options, + region: this.region, + }) let response: Response try { From 7c56c0ffa44d9397c3a491b179d6366ed4305f72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eray=20Ayd=C4=B1n?= Date: Tue, 24 Feb 2026 20:42:44 +0300 Subject: [PATCH 48/71] refactor: extract toError utility function Extract `toError` function of `serverApiClient` to `errors` module. Related-Task: INTER-1488 --- src/errors/toError.ts | 7 +++++++ src/serverApiClient.ts | 13 +++---------- 2 files changed, 10 insertions(+), 10 deletions(-) create mode 100644 src/errors/toError.ts diff --git a/src/errors/toError.ts b/src/errors/toError.ts new file mode 100644 index 00000000..363b3b64 --- /dev/null +++ b/src/errors/toError.ts @@ -0,0 +1,7 @@ +export function toError(e: unknown): Error { + if (e && typeof e === 'object' && 'message' in e) { + return e as Error + } + + return new Error(String(e)) +} diff --git a/src/serverApiClient.ts b/src/serverApiClient.ts index d4ad6eda..555d6df3 100644 --- a/src/serverApiClient.ts +++ b/src/serverApiClient.ts @@ -3,6 +3,7 @@ import { Event, EventUpdate, FingerprintApi, Options, Region, SearchEventsFilter import { paths } from './generatedApiTypes' import { RequestError, SdkError, TooManyRequestsError } from './errors/apiErrors' import { isErrorResponse } from './errors/handleErrorResponse' +import { toError } from './errors/toError' export class FingerprintJsServerApiClient implements FingerprintApi { public readonly region: Region @@ -268,7 +269,7 @@ export class FingerprintJsServerApiClient implements FingerprintApi { try { data = await response.clone().json() } catch (e) { - throw new SdkError('Failed to parse JSON response', response, this.toError(e)) + throw new SdkError('Failed to parse JSON response', response, toError(e)) } return data as SuccessJsonOrVoid } @@ -277,7 +278,7 @@ export class FingerprintJsServerApiClient implements FingerprintApi { try { errPayload = await response.clone().json() } catch (e) { - throw new SdkError('Failed to parse JSON response', response, this.toError(e)) + throw new SdkError('Failed to parse JSON response', response, toError(e)) } if (response.status === 429) { throw new TooManyRequestsError(errPayload, response) @@ -287,12 +288,4 @@ export class FingerprintJsServerApiClient implements FingerprintApi { } throw RequestError.unknown(response) } - - private toError(e: unknown): Error { - if (e && typeof e === 'object' && 'message' in e) { - return e as Error - } - - return new Error(String(e)) - } } From 47f37195810114213aefbb5a6d5f0c74c1c5579e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eray=20Ayd=C4=B1n?= Date: Tue, 24 Feb 2026 21:04:11 +0300 Subject: [PATCH 49/71] docs: add rule_action usage example to getEvent Add a second `@example` block showing how to handle the `rule_action` response when calling `getEvent` with a `rulesetId` parameter. Related-Task: INTER-1488 --- src/serverApiClient.ts | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/serverApiClient.ts b/src/serverApiClient.ts index 555d6df3..600231a4 100644 --- a/src/serverApiClient.ts +++ b/src/serverApiClient.ts @@ -47,10 +47,10 @@ export class FingerprintJsServerApiClient implements FingerprintApi { * @returns {Promise} - promise with event response. For more information, see the [Server API documentation](https://dev.fingerprint.com/reference/getevent). * * @example - * ```javascript + * ```javascript Handling an event * client * .getEvent('') - * .then((result) => console.log(result)) + * .then((event) => console.log(event)) * .catch((error) => { * if (error instanceof RequestError) { * console.log(error.statusCode, error.message) @@ -59,6 +59,23 @@ export class FingerprintJsServerApiClient implements FingerprintApi { * } * }) * ``` + * + * @example Handling an event with rule_action + * ```javascript + * client + * .getEvent('', '') + * .then((event) => { + * const ruleAction = event.products?.rule_action + * if (ruleAction?.type === 'block') { + * console.log('Blocked by rule:', ruleAction.rule_id, ruleAction.status_code) + * } + * }) + * .catch((error) => { + * if (error instanceof RequestError) { + * console.log(error.statusCode, error.message) + * } + * }) + * ``` * */ public async getEvent(eventId: string, rulesetId?: string): Promise { if (!eventId) { From 17862d1c56cad71b044ad2331303a155462ffcae Mon Sep 17 00:00:00 2001 From: Eray AYDIN Date: Tue, 24 Feb 2026 21:06:25 +0300 Subject: [PATCH 50/71] docs: update eventId reference url MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Przemysław Żydek --- src/serverApiClient.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/serverApiClient.ts b/src/serverApiClient.ts index 600231a4..bfc1beb6 100644 --- a/src/serverApiClient.ts +++ b/src/serverApiClient.ts @@ -99,7 +99,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/reference/js-agent-get-function#requestid). + * @param eventId The unique event [identifier](https://docs.fingerprint.com/reference/js-agent-v4-get-function#event_id). * * @return {Promise} * From 799e4fe8b8e0fc384dcfd4f8da0ea1377caf23ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eray=20Ayd=C4=B1n?= Date: Wed, 25 Feb 2026 02:04:07 +0300 Subject: [PATCH 51/71] docs: fix rule_action handling example Fixes `Handling an event with rule_action` example of the `getEvent` 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 bfc1beb6..ece7764c 100644 --- a/src/serverApiClient.ts +++ b/src/serverApiClient.ts @@ -65,7 +65,7 @@ export class FingerprintJsServerApiClient implements FingerprintApi { * client * .getEvent('', '') * .then((event) => { - * const ruleAction = event.products?.rule_action + * const ruleAction = event.rule_action * if (ruleAction?.type === 'block') { * console.log('Blocked by rule:', ruleAction.rule_id, ruleAction.status_code) * } From e3b6d592527c46af2366f962ded5043d54077eff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eray=20Ayd=C4=B1n?= Date: Wed, 25 Feb 2026 02:07:45 +0300 Subject: [PATCH 52/71] docs: fix update event example linked_id usage --- src/serverApiClient.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/serverApiClient.ts b/src/serverApiClient.ts index ece7764c..7ba0514f 100644 --- a/src/serverApiClient.ts +++ b/src/serverApiClient.ts @@ -106,7 +106,7 @@ export class FingerprintJsServerApiClient implements FingerprintApi { * @example * ```javascript * const body = { - * linkedId: 'linked_id', + * linked_id: 'linked_id', * suspect: false, * } * From 8b30052a4cc7bb7aa828fa29b79597571de70195 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eray=20Ayd=C4=B1n?= Date: Wed, 25 Feb 2026 15:38:31 +0300 Subject: [PATCH 53/71] refactor: change rulesetId parameter to options Replace the standalone `rulesetId` string parameter with a `GetEventOptions` object to support future query parameters without breaking changes. Remove the `QueryParamArgs` utility type and its support types that is no longer needed. Related-Task: INTER-1488 --- example/getEvent.mjs | 2 +- readme.md | 2 +- src/serverApiClient.ts | 20 ++++++++++++++----- src/types.ts | 18 +++-------------- .../getEventTests.spec.ts | 2 +- 5 files changed, 21 insertions(+), 23 deletions(-) diff --git a/example/getEvent.mjs b/example/getEvent.mjs index 5eafa49f..71114987 100644 --- a/example/getEvent.mjs +++ b/example/getEvent.mjs @@ -27,7 +27,7 @@ if (envRegion === 'eu') { const client = new FingerprintJsServerApiClient({ region, apiKey }) try { - const event = await client.getEvent(eventId, rulesetId) + const event = await client.getEvent(eventId, { ruleset_id: rulesetId }) console.log(JSON.stringify(event, null, 2)) if (rulesetId && event.rule_action) { diff --git a/readme.md b/readme.md index 2e4c986f..86f9f097 100644 --- a/readme.md +++ b/readme.md @@ -93,7 +93,7 @@ client.getEvent('').then((event) => { }) // Get an event with a ruleset evaluation -client.getEvent('', '').then((event) => { +client.getEvent('', { rulesetId: '' }).then((event) => { console.log(event.rule_action?.type) // 'allow' or 'block' }) diff --git a/src/serverApiClient.ts b/src/serverApiClient.ts index 7ba0514f..921df27d 100644 --- a/src/serverApiClient.ts +++ b/src/serverApiClient.ts @@ -1,5 +1,14 @@ import { AllowedMethod, getRequestPath, GetRequestPathOptions, SuccessJsonOrVoid } from './urlUtils' -import { Event, EventUpdate, FingerprintApi, Options, Region, SearchEventsFilter, SearchEventsResponse } from './types' +import { + Event, + EventUpdate, + FingerprintApi, + GetEventOptions, + Options, + Region, + SearchEventsFilter, + SearchEventsResponse, +} from './types' import { paths } from './generatedApiTypes' import { RequestError, SdkError, TooManyRequestsError } from './errors/apiErrors' import { isErrorResponse } from './errors/handleErrorResponse' @@ -42,7 +51,8 @@ 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 eventId - identifier of the event - * @param rulesetId - optional ruleset ID to evaluate against the event + * @param {object|undefined} options - Optional `getEvent` operation options + * @param {string|undefined} options.ruleset_id - Optional ruleset ID to evaluate against the event * * @returns {Promise} - promise with event response. For more information, see the [Server API documentation](https://dev.fingerprint.com/reference/getevent). * @@ -63,7 +73,7 @@ export class FingerprintJsServerApiClient implements FingerprintApi { * @example Handling an event with rule_action * ```javascript * client - * .getEvent('', '') + * .getEvent('', { ruleset_id: '' }) * .then((event) => { * const ruleAction = event.rule_action * if (ruleAction?.type === 'block') { @@ -77,7 +87,7 @@ export class FingerprintJsServerApiClient implements FingerprintApi { * }) * ``` * */ - public async getEvent(eventId: string, rulesetId?: string): Promise { + public async getEvent(eventId: string, options?: GetEventOptions): Promise { if (!eventId) { throw new TypeError('eventId is not set') } @@ -86,7 +96,7 @@ export class FingerprintJsServerApiClient implements FingerprintApi { path: '/events/{event_id}', pathParams: [eventId], method: 'get', - queryParams: rulesetId ? { ruleset_id: rulesetId } : undefined, + queryParams: options, }) } diff --git a/src/types.ts b/src/types.ts index c51ef285..79a04f80 100644 --- a/src/types.ts +++ b/src/types.ts @@ -43,6 +43,8 @@ export type SearchEventsResponse = components['schemas']['EventSearch'] */ export type Event = components['schemas']['Event'] +export type GetEventOptions = paths['/events/{event_id}']['get']['parameters']['query'] + export type EventUpdate = components['schemas']['EventUpdate'] export type EventRuleAction = components['schemas']['EventRuleAction'] @@ -69,20 +71,6 @@ type ExtractResponse = Path extends { responses: { 200: { content: { 'appl ? R : void -// Utility type to check union -type IsUnion = T extends U ? ([U] extends [T] ? false : true) : never - -// When query params have a single key, flatten it into an optional arg(`getEvent`) else keep as object(`searchEvents`) -// WARN: This type only affects the public FingerprintApi interface (`serverApiClient`). -// For internal request building (`urlUtils`), use `ExtractQueryParams` which preserves the object form. -type QueryParamArgs = [Q] extends [never] - ? [] - : [Exclude] extends [never] - ? [] - : IsUnion> extends false - ? [param?: Exclude[keyof Exclude]] - : [params: Q] - // Extracts args to given API method type ApiMethodArgs = [ // If method has body, extract it as first parameter @@ -90,7 +78,7 @@ type ApiMethodArgs = [ // 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 - ...QueryParamArgs>, + ...(ExtractQueryParams extends never ? [] : [params: ExtractQueryParams]), ] type ApiMethod = ( diff --git a/tests/mocked-responses-tests/getEventTests.spec.ts b/tests/mocked-responses-tests/getEventTests.spec.ts index 274d1f72..7831acbd 100644 --- a/tests/mocked-responses-tests/getEventTests.spec.ts +++ b/tests/mocked-responses-tests/getEventTests.spec.ts @@ -38,7 +38,7 @@ describe('[Mocked response] Get Event', () => { test('with event_id and ruleset_id', async () => { mockFetch.mockReturnValue(Promise.resolve(createResponse(getEventRulesetResponse))) - const response = await client.getEvent(existingEventId, rulesetId) + const response = await client.getEvent(existingEventId, { ruleset_id: rulesetId }) expect(mockFetch).toHaveBeenCalledWith( `https://eu.api.fpjs.io/v4/events/${existingEventId}?ruleset_id=${rulesetId}&ii=${encodeURIComponent(getIntegrationInfo())}`, From d8ebee67b3437c0ae056347ad85208f3a4a56af4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eray=20Ayd=C4=B1n?= Date: Wed, 25 Feb 2026 15:49:11 +0300 Subject: [PATCH 54/71] chore: remove `signature change` related note Removes `Endpoints and method signatures changed.` breaking change note because it no longer true. There are no named parameters in JS and all functions keep similar signature. Related-Task: INTER-1488 --- .changeset/early-seas-look.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/.changeset/early-seas-look.md b/.changeset/early-seas-look.md index 814c9f1c..3f884fc5 100644 --- a/.changeset/early-seas-look.md +++ b/.changeset/early-seas-look.md @@ -13,9 +13,6 @@ **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`, From 4677ab6cf241bd0b1a5b2128a2b63f39cc2fbe75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eray=20Ayd=C4=B1n?= Date: Wed, 25 Feb 2026 16:09:23 +0300 Subject: [PATCH 55/71] refactor: remove retryAfter TooManyRequestsError Remove the `retryAfter` property from the `TooManyRequestsError`. Related-Task: INTER-1488 --- src/errors/apiErrors.ts | 9 --- src/errors/getRetryAfter.ts | 4 -- src/index.ts | 1 - .../deleteVisitorDataTests.spec.ts | 4 -- tests/unit-tests/errorTests.spec.ts | 60 ------------------- 5 files changed, 78 deletions(-) delete mode 100644 src/errors/getRetryAfter.ts delete mode 100644 tests/unit-tests/errorTests.spec.ts diff --git a/src/errors/apiErrors.ts b/src/errors/apiErrors.ts index e6227163..280d6f6d 100644 --- a/src/errors/apiErrors.ts +++ b/src/errors/apiErrors.ts @@ -1,5 +1,4 @@ import { ErrorResponse } from '../types' -import { getRetryAfter } from './getRetryAfter' export class SdkError extends Error { constructor( @@ -46,15 +45,7 @@ export class RequestError extends * Error that indicate that the request was throttled. * */ export class TooManyRequestsError extends RequestError<429, ErrorResponse> { - /** - * Number of seconds to wait before retrying the request. - * @remarks - * The value is parsed from the `Retry-After` header of the response. - */ - readonly retryAfter: number = 0 - constructor(body: ErrorResponse, response: Response) { super(body.error.message, body, 429, body.error.code, response) - this.retryAfter = getRetryAfter(response) } } diff --git a/src/errors/getRetryAfter.ts b/src/errors/getRetryAfter.ts deleted file mode 100644 index 88ba9183..00000000 --- a/src/errors/getRetryAfter.ts +++ /dev/null @@ -1,4 +0,0 @@ -export function getRetryAfter(response: Response) { - const retryAfter = parseInt(response.headers.get('retry-after') ?? '') - return Number.isNaN(retryAfter) ? 0 : retryAfter -} diff --git a/src/index.ts b/src/index.ts index 7247a1e5..8c4573b4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,4 +5,3 @@ export * from './sealedResults' export * from './errors/unsealError' export * from './webhook' export * from './errors/apiErrors' -export * from './errors/getRetryAfter' diff --git a/tests/mocked-responses-tests/deleteVisitorDataTests.spec.ts b/tests/mocked-responses-tests/deleteVisitorDataTests.spec.ts index e609f557..dbc37d9f 100644 --- a/tests/mocked-responses-tests/deleteVisitorDataTests.spec.ts +++ b/tests/mocked-responses-tests/deleteVisitorDataTests.spec.ts @@ -74,15 +74,11 @@ describe('[Mocked response] Delete visitor data', () => { test('429 error', async () => { const mockResponse = new Response(JSON.stringify(Error429), { status: 429, - headers: { - 'retry-after': '5', - }, }) mockFetch.mockReturnValue(Promise.resolve(mockResponse)) const expectedError = new TooManyRequestsError(Error429 as ErrorResponse, mockResponse) await expect(client.deleteVisitorData(existingVisitorId)).rejects.toThrow(expectedError) - expect(expectedError.retryAfter).toEqual(5) }) test('Error with bad JSON', async () => { diff --git a/tests/unit-tests/errorTests.spec.ts b/tests/unit-tests/errorTests.spec.ts deleted file mode 100644 index 933fbd37..00000000 --- a/tests/unit-tests/errorTests.spec.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { ErrorResponse, getRetryAfter, TooManyRequestsError } from '../../src' - -function makeResponse(retryAfter?: string): Response { - const headers = retryAfter != undefined ? { 'retry-after': retryAfter } : ({} as HeadersInit) - - return new Response(null, { - status: 429, - headers, - }) -} - -describe('getRetryAfter', () => { - it('parses numeric retry-after', () => { - const response = makeResponse('120') - expect(getRetryAfter(response)).toBe(120) - }) - - it('returns 0 if there is no header', () => { - const response = makeResponse() - expect(getRetryAfter(response)).toBe(0) - }) - - it('returns 0 if the value is invalid', () => { - const response = makeResponse('not-a-number') - expect(getRetryAfter(response)).toBe(0) - }) - - it('returns 0 if the value is empty string', () => { - const response = makeResponse('') - expect(getRetryAfter(response)).toBe(0) - }) -}) - -describe('TooManyRequestsError', () => { - const body: ErrorResponse = { - error: { - message: 'Too Many Requests', - code: 'too_many_requests', - }, - } - - it('parses retry-after and includes it in the error', () => { - const response = makeResponse('60') - const err = new TooManyRequestsError(body, response) - expect(err).toBeInstanceOf(TooManyRequestsError) - expect(err.retryAfter).toBe(60) - }) - - it('use 0 if there is no header', () => { - const response = makeResponse() - const err = new TooManyRequestsError(body, response) - expect(err.retryAfter).toBe(0) - }) - - it('use 0 if the response has invalid value', () => { - const response = makeResponse('not-a-number') - const err = new TooManyRequestsError(body, response) - expect(err.retryAfter).toBe(0) - }) -}) From 128f963f5a2ab740aeff77e03667584f37480d6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eray=20Ayd=C4=B1n?= Date: Wed, 25 Feb 2026 16:17:54 +0300 Subject: [PATCH 56/71] chore: silence curl output in sync script Make `curl` silent unless `TRACE` or `ACTIONS_STEP_DEBUG` is set. Related-Task: INTER-1488 --- sync.sh | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/sync.sh b/sync.sh index 5bc43919..290d8db4 100755 --- a/sync.sh +++ b/sync.sh @@ -1,8 +1,13 @@ #!/bin/bash set -euo pipefail +CURL_OPTS=(-fSL) +if [[ "${TRACE:-}" != "true" && "${ACTIONS_STEP_DEBUG:-}" != "true" ]]; then + CURL_OPTS+=(-s) +fi + mkdir -p ./resources -curl -fSL -o ./resources/fingerprint-server-api.yaml \ +curl "${CURL_OPTS[@]}" -o ./resources/fingerprint-server-api.yaml \ https://fingerprintjs.github.io/fingerprint-pro-server-api-openapi/schemas/fingerprint-server-api-v4.yaml examplesList=( @@ -28,7 +33,7 @@ for example in "${examplesList[@]}"; do mkdir -p "$destinationDir" echo "Downloading $example to $destinationPath" - curl -fSL -o "$destinationPath" "https://fingerprintjs.github.io/fingerprint-pro-server-api-openapi/examples/$example" + curl "${CURL_OPTS[@]}" -o "$destinationPath" "https://fingerprintjs.github.io/fingerprint-pro-server-api-openapi/examples/$example" done echo "All OpenAPI documentation downloads complete." From 5fbe415ee25391276ef392305a39168ca5063de8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eray=20Ayd=C4=B1n?= Date: Wed, 25 Feb 2026 19:53:41 +0300 Subject: [PATCH 57/71] refactor: rename createResponse in test utils Rename the test helper to better reflect that it creates a JSON response. Related-Task: INTER-1488 --- tests/mocked-responses-tests/getEventTests.spec.ts | 12 ++++++------ .../searchEventsTests.spec.ts | 14 +++++++------- tests/mocked-responses-tests/utils.ts | 2 +- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/tests/mocked-responses-tests/getEventTests.spec.ts b/tests/mocked-responses-tests/getEventTests.spec.ts index 7831acbd..251391e4 100644 --- a/tests/mocked-responses-tests/getEventTests.spec.ts +++ b/tests/mocked-responses-tests/getEventTests.spec.ts @@ -8,7 +8,7 @@ import { } from '../../src' import getEventResponse from './mocked-responses-data/events/get_event_200.json' import getEventRulesetResponse from './mocked-responses-data/events/get_event_ruleset_200.json' -import { createResponse } from './utils' +import { createJsonResponse } from './utils' jest.spyOn(global, 'fetch') @@ -21,7 +21,7 @@ describe('[Mocked response] Get Event', () => { const client = new FingerprintJsServerApiClient({ region: Region.EU, apiKey }) test('with event_id', async () => { - mockFetch.mockReturnValue(Promise.resolve(createResponse(getEventResponse))) + mockFetch.mockReturnValue(Promise.resolve(createJsonResponse(getEventResponse))) const response = await client.getEvent(existingEventId) @@ -36,7 +36,7 @@ describe('[Mocked response] Get Event', () => { }) test('with event_id and ruleset_id', async () => { - mockFetch.mockReturnValue(Promise.resolve(createResponse(getEventRulesetResponse))) + mockFetch.mockReturnValue(Promise.resolve(createJsonResponse(getEventRulesetResponse))) const response = await client.getEvent(existingEventId, { ruleset_id: rulesetId }) @@ -57,7 +57,7 @@ describe('[Mocked response] Get Event', () => { message: 'secret key is required', }, } satisfies ErrorResponse - const mockResponse = createResponse(errorInfo, 403) + const mockResponse = createJsonResponse(errorInfo, 403) mockFetch.mockReturnValue(Promise.resolve(mockResponse)) await expect(client.getEvent(existingEventId)).rejects.toThrow( RequestError.fromErrorResponse(errorInfo, mockResponse) @@ -71,7 +71,7 @@ describe('[Mocked response] Get Event', () => { message: 'request id is not found', }, } satisfies ErrorResponse - const mockResponse = createResponse(errorInfo, 404) + const mockResponse = createJsonResponse(errorInfo, 404) mockFetch.mockReturnValue(Promise.resolve(mockResponse)) await expect(client.getEvent(existingEventId)).rejects.toThrow( RequestError.fromErrorResponse(errorInfo, mockResponse) @@ -79,7 +79,7 @@ describe('[Mocked response] Get Event', () => { }) test('Error with unknown', async () => { - const mockResponse = createResponse( + const mockResponse = createJsonResponse( { error: 'Unexpected error format', }, diff --git a/tests/mocked-responses-tests/searchEventsTests.spec.ts b/tests/mocked-responses-tests/searchEventsTests.spec.ts index 8ee4edbc..5a83c026 100644 --- a/tests/mocked-responses-tests/searchEventsTests.spec.ts +++ b/tests/mocked-responses-tests/searchEventsTests.spec.ts @@ -6,7 +6,7 @@ import { SearchEventsFilter, } from '../../src' import getEventsSearch from './mocked-responses-data/events/search/get_event_search_200.json' -import { createResponse } from './utils' +import { createJsonResponse } from './utils' jest.spyOn(global, 'fetch') @@ -17,7 +17,7 @@ describe('[Mocked response] Search Events', () => { const client = new FingerprintJsServerApiClient({ apiKey }) test('without filter', async () => { - mockFetch.mockReturnValue(Promise.resolve(createResponse(getEventsSearch))) + mockFetch.mockReturnValue(Promise.resolve(createJsonResponse(getEventsSearch))) const limit = 10 @@ -35,7 +35,7 @@ describe('[Mocked response] Search Events', () => { }) test('with filter params passed as undefined', async () => { - mockFetch.mockReturnValue(Promise.resolve(createResponse(getEventsSearch))) + mockFetch.mockReturnValue(Promise.resolve(createJsonResponse(getEventsSearch))) const limit = 10 @@ -55,7 +55,7 @@ describe('[Mocked response] Search Events', () => { }) test('with partial filter', async () => { - mockFetch.mockReturnValue(Promise.resolve(createResponse(getEventsSearch))) + mockFetch.mockReturnValue(Promise.resolve(createJsonResponse(getEventsSearch))) const limit = 10 const bot = 'good' @@ -77,7 +77,7 @@ describe('[Mocked response] Search Events', () => { }) test('with all possible filters', async () => { - mockFetch.mockReturnValue(Promise.resolve(createResponse(getEventsSearch))) + mockFetch.mockReturnValue(Promise.resolve(createJsonResponse(getEventsSearch))) const filters: SearchEventsFilter = { limit: 10, @@ -148,7 +148,7 @@ describe('[Mocked response] Search Events', () => { message: 'Forbidden', }, } satisfies ErrorResponse - const mockResponse = createResponse(error, 400) + const mockResponse = createJsonResponse(error, 400) mockFetch.mockReturnValue(Promise.resolve(mockResponse)) await expect( client.searchEvents({ @@ -164,7 +164,7 @@ describe('[Mocked response] Search Events', () => { message: 'secret key is required', }, } satisfies ErrorResponse - const mockResponse = createResponse(error, 403) + const mockResponse = createJsonResponse(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 index d62a975c..4b4d40ea 100644 --- a/tests/mocked-responses-tests/utils.ts +++ b/tests/mocked-responses-tests/utils.ts @@ -1,4 +1,4 @@ -export const createResponse = (resp: object, status: number = 200) => +export const createJsonResponse = (resp: object, status: number = 200) => new Response(JSON.stringify(resp), { headers: { 'content-type': 'application/json', From 7c3b65a71bb48adafa675a9841ab45a536eae0f5 Mon Sep 17 00:00:00 2001 From: Eray AYDIN Date: Wed, 25 Feb 2026 20:06:50 +0300 Subject: [PATCH 58/71] chore: add `RULESET_ID` to .env.example file Co-authored-by: Juraj Uhlar --- example/.env.example | 1 + 1 file changed, 1 insertion(+) diff --git a/example/.env.example b/example/.env.example index c48a2f10..4f806c18 100644 --- a/example/.env.example +++ b/example/.env.example @@ -1,6 +1,7 @@ API_KEY= VISITOR_ID= EVENT_ID= +RULESET_ID= # "eu" or "ap", "us" is the default REGION= WEBHOOK_SIGNATURE_SECRET= From c233de2ac7c28e78275f3ea357c090d039565477 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eray=20Ayd=C4=B1n?= Date: Wed, 25 Feb 2026 20:08:09 +0300 Subject: [PATCH 59/71] docs: fix pagination_key usage example --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 86f9f097..afadaba2 100644 --- a/readme.md +++ b/readme.md @@ -101,7 +101,7 @@ client.getEvent('', { rulesetId: '' }).then((event) => { client .searchEvents({ limit: 10, -// pagination_key: previousSearchResult.paginationKey, +// pagination_key: previousSearchResult.pagination_key, suspect: true, }) .then((events) => { From 8484c2c83722bdad081ba671af19eb378b734641 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eray=20Ayd=C4=B1n?= Date: Wed, 25 Feb 2026 20:10:44 +0300 Subject: [PATCH 60/71] docs: fix ruleset evaluation example in readme --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index afadaba2..866ce3d6 100644 --- a/readme.md +++ b/readme.md @@ -93,7 +93,7 @@ client.getEvent('').then((event) => { }) // Get an event with a ruleset evaluation -client.getEvent('', { rulesetId: '' }).then((event) => { +client.getEvent('', { ruleset_id: '' }).then((event) => { console.log(event.rule_action?.type) // 'allow' or 'block' }) From 26e1f3ef0d2f02cc53e45d9657d9546bd8a86ff6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eray=20Ayd=C4=B1n?= Date: Wed, 25 Feb 2026 20:14:26 +0300 Subject: [PATCH 61/71] feat: update OpenAPI schema Updates OpenAPI schema to latest version. Related-Task: INTER-1488 --- resources/fingerprint-server-api.yaml | 34 ++--- src/generatedApiTypes.ts | 34 ++--- .../events/get_event_200.json | 122 ++++++++++++++++-- .../events/get_event_ruleset_200.json | 2 +- .../events/search/get_event_search_200.json | 108 +++++++++++++++- .../webhook/webhook_event.json | 108 +++++++++++++++- 6 files changed, 360 insertions(+), 48 deletions(-) diff --git a/resources/fingerprint-server-api.yaml b/resources/fingerprint-server-api.yaml index 7e23e15a..db15fe10 100644 --- a/resources/fingerprint-server-api.yaml +++ b/resources/fingerprint-server-api.yaml @@ -23,7 +23,7 @@ tags: analysis events or event history of individual visitors. externalDocs: description: API documentation - url: https://dev.fingerprint.com/reference/pro-server-api + url: https://docs.fingerprint.com/reference/server-api servers: - url: https://api.fpjs.io/v4 description: Global @@ -55,7 +55,7 @@ paths: type: string description: >- The unique - [identifier](https://dev.fingerprint.com/reference/get-function#requestid) + [identifier](https://docs.fingerprint.com/reference/js-agent-v4-get-function#event_id) of each identification request (`requestId` can be used in its place). - name: ruleset_id @@ -140,7 +140,7 @@ paths: type: string description: >- The unique event - [identifier](https://dev.fingerprint.com/reference/get-function#event_id). + [identifier](https://docs.fingerprint.com/reference/js-agent-v4-get-function#event_id). requestBody: required: true content: @@ -268,7 +268,7 @@ paths: type: string description: > Unique [visitor - identifier](https://dev.fingerprint.com/reference/get-function#visitorid) + identifier](https://docs.fingerprint.com/reference/js-agent-v4-get-function#visitor_id) issued by Fingerprint Identification and all active Smart Signals. Filter for events matching this `visitor_id`. @@ -313,8 +313,8 @@ paths: You can use [linked - Ids](https://dev.fingerprint.com/reference/get-function#linkedid) to - associate identification requests with your own identifier, for + Ids](https://docs.fingerprint.com/reference/js-agent-v4-get-function#linkedid) + to associate identification requests with your own identifier, for example, session Id, purchase Id, or transaction Id. You can then use this `linked_id` parameter to retrieve all events associated with your custom identifier. @@ -372,7 +372,7 @@ paths: type: boolean description: > Filter events previously tagged as suspicious via the [Update - API](https://dev.fingerprint.com/reference/updateevent). + API](https://docs.fingerprint.com/reference/server-api-v4-update-event). > Note: When using this parameter, only events with the `suspect` property explicitly set to `true` or `false` are returned. Events @@ -688,7 +688,7 @@ paths: - Upon request to delete, this data is deleted asynchronously (typically within a few minutes) and it will no longer be used to identify this browser (or device) for your [Fingerprint - Workspace](https://dev.fingerprint.com/docs/glossary#fingerprint-workspace). + Workspace](https://docs.fingerprint.com/docs/glossary#fingerprint-workspace). #### Identification requests made from this browser (or device) @@ -696,7 +696,7 @@ paths: - Fingerprint stores the identification requests made from a browser (or device) for up to 30 (or 90) days depending on your plan. To learn more, see [Data - Retention](https://dev.fingerprint.com/docs/regions#data-retention). + Retention](https://docs.fingerprint.com/docs/regions#data-retention). - Upon request to delete, the identification requests that were made by this browser @@ -711,9 +711,9 @@ paths: a different visitor 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. + API](https://docs.fingerprint.com/reference/server-api-v4-get-event) + with an `event_id` that was made outside of the 10 days, you will still + receive a valid response. ### Interested? @@ -728,7 +728,7 @@ paths: type: string description: >- The [visitor - ID](https://dev.fingerprint.com/reference/get-function#visitorid) + ID](https://docs.fingerprint.com/reference/js-agent-v4-get-function#visitor_id) you want to delete. responses: '200': @@ -791,7 +791,7 @@ components: 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). + endpoint](https://docs.fingerprint.com/reference/server-api-v4-update-event). Integration: type: object properties: @@ -1113,7 +1113,7 @@ components: 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) + Detection](https://docs.fingerprint.com/docs/smart-signals-reference#factory-reset-detection) to learn more about this Smart Signal. Frida: type: boolean @@ -1309,7 +1309,7 @@ components: * `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) + Detection](https://docs.fingerprint.com/docs/smart-signals-reference#mitm-attack-detection) to learn more about this Smart Signal. PrivacySettings: type: boolean @@ -1452,7 +1452,7 @@ components: 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 + See more details here: https://docs.fingerprint.com/docs/suspect-score Tampering: type: boolean description: > diff --git a/src/generatedApiTypes.ts b/src/generatedApiTypes.ts index d5953d43..ef8d18da 100644 --- a/src/generatedApiTypes.ts +++ b/src/generatedApiTypes.ts @@ -96,10 +96,10 @@ export interface paths { * * #### Browser (or device) properties * - Represents the data that Fingerprint collected from this specific browser (or device) and everything inferred and derived from it. - * - Upon request to delete, this data is deleted asynchronously (typically within a few minutes) and it will no longer be used to identify this browser (or device) for your [Fingerprint Workspace](https://dev.fingerprint.com/docs/glossary#fingerprint-workspace). + * - Upon request to delete, this data is deleted asynchronously (typically within a few minutes) and it will no longer be used to identify this browser (or device) for your [Fingerprint Workspace](https://docs.fingerprint.com/docs/glossary#fingerprint-workspace). * * #### Identification requests made from this browser (or device) - * - Fingerprint stores the identification requests made from a browser (or device) for up to 30 (or 90) days depending on your plan. To learn more, see [Data Retention](https://dev.fingerprint.com/docs/regions#data-retention). + * - Fingerprint stores the identification requests made from a browser (or device) for up to 30 (or 90) days depending on your plan. To learn more, see [Data Retention](https://docs.fingerprint.com/docs/regions#data-retention). * - Upon request to delete, the identification requests that were made by this browser * - Within the past 10 days are deleted within 24 hrs. * - Outside of 10 days are allowed to purge as per your data retention period. @@ -107,7 +107,7 @@ export interface paths { * ### Corollary * After requesting to delete a visitor ID, * - If the same browser (or device) requests to identify, it will receive a different visitor ID. - * - If you request [`/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 [`/v4/events` API](https://docs.fingerprint.com/reference/server-api-v4-get-event) with an `event_id` that was made outside of the 10 days, you will still receive a valid response. * * ### Interested? * Please [contact our support team](https://fingerprint.com/support/) to enable it for you. Otherwise, you will receive a 403. @@ -136,7 +136,7 @@ export interface components { /** @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). */ + /** @description Field is `true` if you have previously set the `suspect` flag for this event using the [Server API Update event endpoint](https://docs.fingerprint.com/reference/server-api-v4-update-event). */ Suspect: boolean Integration: { /** @description The name of the specific integration, e.g. "fingerprint-pro-react". */ @@ -316,7 +316,7 @@ export interface components { 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. + * @description The time of the most recent factory reset that happened on the **mobile device** is expressed as Unix epoch time. When a factory reset cannot be detected on the mobile device or when the request is initiated from a browser, this field will correspond to the *epoch* time (i.e 1 Jan 1970 UTC) as a value of 0. See [Factory Reset Detection](https://docs.fingerprint.com/docs/smart-signals-reference#factory-reset-detection) to learn more about this Smart Signal. * */ FactoryReset: number @@ -421,7 +421,7 @@ export interface components { 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. + * See [MitM Attack Detection](https://docs.fingerprint.com/docs/smart-signals-reference#mitm-attack-detection) to learn more about this Smart Signal. * */ MitMAttack: boolean /** @description `true` if the request is from a privacy aware browser (e.g. Tor) or from a browser in which fingerprinting is blocked. Otherwise `false`. @@ -500,7 +500,7 @@ export interface components { } /** @description Describes the action the client should take, according to the rule in the ruleset that matched the event. When getting an event by event ID, the rule_action will only be included when the ruleset_id query parameter is specified. */ EventRuleAction: components['schemas']['EventRuleActionAllow'] | components['schemas']['EventRuleActionBlock'] - /** @description Suspect Score is an easy way to integrate Smart Signals into your fraud protection work flow. It is a weighted representation of all Smart Signals present in the payload that helps identify suspicious activity. The value range is [0; S] where S is sum of all Smart Signals weights. See more details here: https://dev.fingerprint.com/docs/suspect-score + /** @description Suspect Score is an easy way to integrate Smart Signals into your fraud protection work flow. It is a weighted representation of all Smart Signals present in the payload that helps identify suspicious activity. The value range is [0; S] where S is sum of all Smart Signals weights. See more details here: https://docs.fingerprint.com/docs/suspect-score * */ SuspectScore: number /** @description Flag indicating browser tampering was detected. This happens when either: @@ -828,7 +828,7 @@ export interface components { /** @description Environment Id of the event. For example: `ae_47abaca3db2c7c43` * */ 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). */ + /** @description Field is `true` if you have previously set the `suspect` flag for this event using the [Server API Update event endpoint](https://docs.fingerprint.com/reference/server-api-v4-update-event). */ suspect?: components['schemas']['Suspect'] /** @description Contains information about the SDK used to perform the request. */ sdk?: components['schemas']['SDK'] @@ -883,7 +883,7 @@ export interface components { * * `false` - No signs of emulated environment detected or the client is not Android. * */ 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. + /** @description The time of the most recent factory reset that happened on the **mobile device** is expressed as Unix epoch time. When a factory reset cannot be detected on the mobile device or when the request is initiated from a browser, this field will correspond to the *epoch* time (i.e 1 Jan 1970 UTC) as a value of 0. See [Factory Reset Detection](https://docs.fingerprint.com/docs/smart-signals-reference#factory-reset-detection) to learn more about this Smart Signal. * */ factory_reset_timestamp?: components['schemas']['FactoryReset'] /** @description [Frida](https://frida.re/docs/) detection for Android and iOS devices. There are 2 values: @@ -914,7 +914,7 @@ export interface components { 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. + * See [MitM Attack Detection](https://docs.fingerprint.com/docs/smart-signals-reference#mitm-attack-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`. @@ -927,7 +927,7 @@ export interface components { root_apps?: components['schemas']['RootApps'] /** @description Describes the action the client should take, according to the rule in the ruleset that matched the event. When getting an event by event ID, the rule_action will only be included when the ruleset_id query parameter is specified. */ rule_action?: components['schemas']['EventRuleAction'] - /** @description Suspect Score is an easy way to integrate Smart Signals into your fraud protection work flow. It is a weighted representation of all Smart Signals present in the payload that helps identify suspicious activity. The value range is [0; S] where S is sum of all Smart Signals weights. See more details here: https://dev.fingerprint.com/docs/suspect-score + /** @description Suspect Score is an easy way to integrate Smart Signals into your fraud protection work flow. It is a weighted representation of all Smart Signals present in the payload that helps identify suspicious activity. The value range is [0; S] where S is sum of all Smart Signals weights. See more details here: https://docs.fingerprint.com/docs/suspect-score * */ suspect_score?: components['schemas']['SuspectScore'] /** @description Flag indicating browser tampering was detected. This happens when either: @@ -1126,7 +1126,7 @@ export interface operations { } header?: never path: { - /** @description The unique [identifier](https://dev.fingerprint.com/reference/get-function#requestid) of each identification request (`requestId` can be used in its place). */ + /** @description The unique [identifier](https://docs.fingerprint.com/reference/js-agent-v4-get-function#event_id) of each identification request (`requestId` can be used in its place). */ event_id: string } cookie?: never @@ -1194,7 +1194,7 @@ export interface operations { query?: never header?: never path: { - /** @description The unique event [identifier](https://dev.fingerprint.com/reference/get-function#event_id). */ + /** @description The unique event [identifier](https://docs.fingerprint.com/reference/js-agent-v4-get-function#event_id). */ event_id: string } cookie?: never @@ -1264,7 +1264,7 @@ export interface operations { * 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 Identification and all active Smart Signals. + /** @description Unique [visitor identifier](https://docs.fingerprint.com/reference/js-agent-v4-get-function#visitor_id) issued by Fingerprint Identification and all active Smart Signals. * Filter for events matching this `visitor_id`. * */ visitor_id?: string @@ -1286,7 +1286,7 @@ export interface operations { asn?: string /** @description Filter events by your custom identifier. * - * You can use [linked Ids](https://dev.fingerprint.com/reference/get-function#linkedid) to associate identification requests with your own identifier, for example, session Id, purchase Id, or transaction Id. You can then use this `linked_id` parameter to retrieve all events associated with your custom identifier. + * You can use [linked Ids](https://docs.fingerprint.com/reference/js-agent-v4-get-function#linkedid) to associate identification requests with your own identifier, for example, session Id, purchase Id, or transaction Id. You can then use this `linked_id` parameter to retrieve all events associated with your custom identifier. * */ linked_id?: string /** @description Filter events by the URL (`url` property) associated with the event. @@ -1310,7 +1310,7 @@ export interface operations { /** @description Sort events in reverse timestamp order. * */ reverse?: boolean - /** @description Filter events previously tagged as suspicious via the [Update API](https://dev.fingerprint.com/reference/updateevent). + /** @description Filter events previously tagged as suspicious via the [Update API](https://docs.fingerprint.com/reference/server-api-v4-update-event). * > Note: When using this parameter, only events with the `suspect` property explicitly set to `true` or `false` are returned. Events with undefined `suspect` property are left out of the response. * */ suspect?: boolean @@ -1462,7 +1462,7 @@ export interface operations { query?: never header?: never path: { - /** @description The [visitor ID](https://dev.fingerprint.com/reference/get-function#visitorid) you want to delete. */ + /** @description The [visitor ID](https://docs.fingerprint.com/reference/js-agent-v4-get-function#visitor_id) you want to delete. */ visitor_id: string } cookie?: never 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 index 5cb733e5..8985aa0e 100644 --- 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 @@ -6,6 +6,7 @@ "url": "https://www.example.com/login?hope{this{works[!", "ip_address": "61.127.217.15", "user_agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64) ....", + "client_referrer": "https://example.com/blog/my-article", "browser_details": { "browser_name": "Chrome", "browser_major_version": "74", @@ -34,6 +35,11 @@ "first_seen_at": 1708102555327, "last_seen_at": 1708102555327 }, + "proximity": { + "id": "w1aTfd4MCvl", + "precision_radius": 10, + "confidence": 0.95 + }, "bot": "not_detected", "root_apps": false, "emulator": false, @@ -61,6 +67,7 @@ "asn": "7922", "asn_name": "COMCAST-7922", "asn_network": "73.136.0.0/13", + "asn_type": "isp", "datacenter_result": true, "datacenter_name": "DediPath" }, @@ -87,6 +94,7 @@ "asn": "6805", "asn_name": "Telefonica Germany", "asn_network": "2a02:3100::/24", + "asn_type": "isp", "datacenter_result": false, "datacenter_name": "" } @@ -100,22 +108,23 @@ "proxy_confidence": "low", "proxy_details": { "proxy_type": "residential", - "last_seen_at": 1708102555327 + "last_seen_at": 1708102555327, + "provider": "Massive" }, "vpn": false, "vpn_confidence": "high", "vpn_origin_timezone": "Europe/Berlin", "vpn_origin_country": "unknown", "vpn_methods": { - "timezone_mismatch": false, - "public_vpn": false, - "auxiliary_mobile": false, - "os_mismatch": false, - "relay": false + "timezone_mismatch": false, + "public_vpn": false, + "auxiliary_mobile": false, + "os_mismatch": false, + "relay": false }, "incognito": false, "tampering": false, - "tampering_details" : { + "tampering_details": { "anomaly_score": 0.1955, "anti_detect_browser": false }, @@ -174,5 +183,100 @@ } ] }, - "replayed": false -} + "replayed": false, + "high_activity_device": false, + "raw_device_attributes": { + "math": "5f030fa7d2e5f9f757bfaf81642eb1a6", + "vendor": "Google Inc.", + "plugins": [ + { + "description": "Portable Document Format", + "mimeTypes": [ + { + "suffixes": "pdf", + "type": "application/pdf" + }, + { + "suffixes": "pdf", + "type": "text/pdf" + } + ], + "name": "PDF Viewer" + } + ], + "webgl_extensions": { + "context_attributes": "6b1ed336830d2bc96442a9d76373252a", + "extension_parameters": "86a8abb36f0cb30b5946dec0c761d042", + "extensions": "57233d7b10f89fcd1ff95e3837ccd72d", + "parameters": "ea118c48e308bc4b0677118bbb3019ec", + "shader_precisions": "f223dfbcd580cf142da156d93790eb83", + "unsupported_extensions": [] + }, + "cookies_enabled": true, + "webgl_basics": { + "renderer": "WebKit WebGL", + "renderer_unmasked": "ANGLE (Apple, ANGLE Metal Renderer: Apple M4, Unspecified Version)", + "shading_language_version": "WebGL GLSL ES 1.0 (OpenGL ES GLSL ES 1.0 Chromium)", + "vendor": "WebKit", + "vendor_unmasked": "Google Inc. (Apple)", + "version": "WebGL 1.0 (OpenGL ES 2.0 Chromium)" + }, + "canvas": { + "geometry": "db3c1462576a399a03ae93d0ab9eb5c4", + "text": "70c3d3f7eb4408dc37a6bf8af1c51029", + "winding": true + }, + "hardware_concurrency": 10, + "languages": [ + [ + "en-US" + ] + ], + "color_depth": 24, + "fonts": [ + "Arial Unicode MS", + "Gill Sans", + "Helvetica Neue", + "Menlo" + ], + "indexed_db": true, + "touch_support": { + "max_touch_points": 0, + "touch_event": false, + "touch_start": false + }, + "device_memory": 8, + "oscpu": "Windows NT 6.1; Win64; x64", + "architecture": 127, + "screen_resolution": [ + 1920, + 1080 + ], + "timezone": "America/Sao_Paulo", + "emoji": { + "bottom": 32, + "font": "Times", + "height": 18, + "left": 8, + "right": 1608, + "top": 14, + "width": 1600, + "x": 8, + "y": 14 + }, + "font_preferences": { + "apple": 147.5625, + "default": 147.5625, + "min": 9.234375, + "mono": 133.0625, + "sans": 144.015625, + "serif": 147.5625, + "system": 146.09375 + }, + "platform": "MacIntel", + "local_storage": true, + "session_storage": true, + "date_time_locale": "en-US", + "audio": 124.04347745512496 + } +} \ No newline at end of file diff --git a/tests/mocked-responses-tests/mocked-responses-data/events/get_event_ruleset_200.json b/tests/mocked-responses-tests/mocked-responses-data/events/get_event_ruleset_200.json index f931e47f..b02bfb2d 100644 --- a/tests/mocked-responses-tests/mocked-responses-data/events/get_event_ruleset_200.json +++ b/tests/mocked-responses-tests/mocked-responses-data/events/get_event_ruleset_200.json @@ -293,4 +293,4 @@ "rule_id": "r_uE0af8497PFAOD", "rule_expression": "bot in [\"bad\"] || incognito" } -} +} \ No newline at end of file diff --git a/tests/mocked-responses-tests/mocked-responses-data/events/search/get_event_search_200.json b/tests/mocked-responses-tests/mocked-responses-data/events/search/get_event_search_200.json index f72e99d1..2affda77 100644 --- 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 @@ -8,6 +8,7 @@ "url": "https://www.example.com/login?hope{this{works[!", "ip_address": "61.127.217.15", "user_agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64) ....", + "client_referrer": "https://example.com/blog/my-article", "browser_details": { "browser_name": "Chrome", "browser_major_version": "74", @@ -36,6 +37,11 @@ "first_seen_at": 1708102555327, "last_seen_at": 1708102555327 }, + "proximity": { + "id": "w1aTfd4MCvl", + "precision_radius": 10, + "confidence": 0.95 + }, "bot": "not_detected", "root_apps": false, "emulator": false, @@ -63,6 +69,7 @@ "asn": "7922", "asn_name": "COMCAST-7922", "asn_network": "73.136.0.0/13", + "asn_type": "isp", "datacenter_result": true, "datacenter_name": "DediPath" }, @@ -89,6 +96,7 @@ "asn": "6805", "asn_name": "Telefonica Germany", "asn_network": "2a02:3100::/24", + "asn_type": "isp", "datacenter_result": false, "datacenter_name": "" } @@ -102,7 +110,8 @@ "proxy_confidence": "low", "proxy_details": { "proxy_type": "residential", - "last_seen_at": 1708102555327 + "last_seen_at": 1708102555327, + "provider": "Massive" }, "vpn": false, "vpn_confidence": "high", @@ -176,7 +185,102 @@ } ] }, - "replayed": false + "replayed": false, + "high_activity_device": false, + "raw_device_attributes": { + "math": "5f030fa7d2e5f9f757bfaf81642eb1a6", + "vendor": "Google Inc.", + "plugins": [ + { + "description": "Portable Document Format", + "mimeTypes": [ + { + "suffixes": "pdf", + "type": "application/pdf" + }, + { + "suffixes": "pdf", + "type": "text/pdf" + } + ], + "name": "PDF Viewer" + } + ], + "webgl_extensions": { + "context_attributes": "6b1ed336830d2bc96442a9d76373252a", + "extension_parameters": "86a8abb36f0cb30b5946dec0c761d042", + "extensions": "57233d7b10f89fcd1ff95e3837ccd72d", + "parameters": "ea118c48e308bc4b0677118bbb3019ec", + "shader_precisions": "f223dfbcd580cf142da156d93790eb83", + "unsupported_extensions": [] + }, + "cookies_enabled": true, + "webgl_basics": { + "renderer": "WebKit WebGL", + "renderer_unmasked": "ANGLE (Apple, ANGLE Metal Renderer: Apple M4, Unspecified Version)", + "shading_language_version": "WebGL GLSL ES 1.0 (OpenGL ES GLSL ES 1.0 Chromium)", + "vendor": "WebKit", + "vendor_unmasked": "Google Inc. (Apple)", + "version": "WebGL 1.0 (OpenGL ES 2.0 Chromium)" + }, + "canvas": { + "geometry": "db3c1462576a399a03ae93d0ab9eb5c4", + "text": "70c3d3f7eb4408dc37a6bf8af1c51029", + "winding": true + }, + "hardware_concurrency": 10, + "languages": [ + [ + "en-US" + ] + ], + "color_depth": 24, + "fonts": [ + "Arial Unicode MS", + "Gill Sans", + "Helvetica Neue", + "Menlo" + ], + "indexed_db": true, + "touch_support": { + "max_touch_points": 0, + "touch_event": false, + "touch_start": false + }, + "device_memory": 8, + "oscpu": "Windows NT 6.1; Win64; x64", + "architecture": 127, + "screen_resolution": [ + 1920, + 1080 + ], + "timezone": "America/Sao_Paulo", + "emoji": { + "bottom": 32, + "font": "Times", + "height": 18, + "left": 8, + "right": 1608, + "top": 14, + "width": 1600, + "x": 8, + "y": 14 + }, + "font_preferences": { + "apple": 147.5625, + "default": 147.5625, + "min": 9.234375, + "mono": 133.0625, + "sans": 144.015625, + "serif": 147.5625, + "system": 146.09375 + }, + "platform": "MacIntel", + "local_storage": true, + "session_storage": true, + "date_time_locale": "en-US", + "audio": 124.04347745512496 + } } ], "pagination_key": "1708102555327" 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 index 09c15380..d9e597b2 100644 --- a/tests/mocked-responses-tests/mocked-responses-data/webhook/webhook_event.json +++ b/tests/mocked-responses-tests/mocked-responses-data/webhook/webhook_event.json @@ -6,6 +6,7 @@ "url": "https://www.example.com/login?hope{this{works[!", "ip_address": "61.127.217.15", "user_agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64) ....", + "client_referrer": "https://example.com/blog/my-article", "browser_details": { "browser_name": "Chrome", "browser_major_version": "74", @@ -34,6 +35,11 @@ "first_seen_at": 1708102555327, "last_seen_at": 1708102555327 }, + "proximity": { + "id": "w1aTfd4MCvl", + "precision_radius": 10, + "confidence": 0.95 + }, "bot": "not_detected", "root_apps": false, "emulator": false, @@ -61,6 +67,7 @@ "asn": "7922", "asn_name": "COMCAST-7922", "asn_network": "73.136.0.0/13", + "asn_type": "isp", "datacenter_result": true, "datacenter_name": "DediPath" }, @@ -87,6 +94,7 @@ "asn": "6805", "asn_name": "Telefonica Germany", "asn_network": "2a02:3100::/24", + "asn_type": "isp", "datacenter_result": false, "datacenter_name": "" } @@ -100,7 +108,8 @@ "proxy_confidence": "low", "proxy_details": { "proxy_type": "residential", - "last_seen_at": 1708102555327 + "last_seen_at": 1708102555327, + "provider": "Massive" }, "vpn": false, "vpn_confidence": "high", @@ -174,5 +183,100 @@ } ] }, - "replayed": false + "replayed": false, + "high_activity_device": false, + "raw_device_attributes": { + "math": "5f030fa7d2e5f9f757bfaf81642eb1a6", + "vendor": "Google Inc.", + "plugins": [ + { + "description": "Portable Document Format", + "mimeTypes": [ + { + "suffixes": "pdf", + "type": "application/pdf" + }, + { + "suffixes": "pdf", + "type": "text/pdf" + } + ], + "name": "PDF Viewer" + } + ], + "webgl_extensions": { + "context_attributes": "6b1ed336830d2bc96442a9d76373252a", + "extension_parameters": "86a8abb36f0cb30b5946dec0c761d042", + "extensions": "57233d7b10f89fcd1ff95e3837ccd72d", + "parameters": "ea118c48e308bc4b0677118bbb3019ec", + "shader_precisions": "f223dfbcd580cf142da156d93790eb83", + "unsupported_extensions": [] + }, + "cookies_enabled": true, + "webgl_basics": { + "renderer": "WebKit WebGL", + "renderer_unmasked": "ANGLE (Apple, ANGLE Metal Renderer: Apple M4, Unspecified Version)", + "shading_language_version": "WebGL GLSL ES 1.0 (OpenGL ES GLSL ES 1.0 Chromium)", + "vendor": "WebKit", + "vendor_unmasked": "Google Inc. (Apple)", + "version": "WebGL 1.0 (OpenGL ES 2.0 Chromium)" + }, + "canvas": { + "geometry": "db3c1462576a399a03ae93d0ab9eb5c4", + "text": "70c3d3f7eb4408dc37a6bf8af1c51029", + "winding": true + }, + "hardware_concurrency": 10, + "languages": [ + [ + "en-US" + ] + ], + "color_depth": 24, + "fonts": [ + "Arial Unicode MS", + "Gill Sans", + "Helvetica Neue", + "Menlo" + ], + "indexed_db": true, + "touch_support": { + "max_touch_points": 0, + "touch_event": false, + "touch_start": false + }, + "device_memory": 8, + "oscpu": "Windows NT 6.1; Win64; x64", + "architecture": 127, + "screen_resolution": [ + 1920, + 1080 + ], + "timezone": "America/Sao_Paulo", + "emoji": { + "bottom": 32, + "font": "Times", + "height": 18, + "left": 8, + "right": 1608, + "top": 14, + "width": 1600, + "x": 8, + "y": 14 + }, + "font_preferences": { + "apple": 147.5625, + "default": 147.5625, + "min": 9.234375, + "mono": 133.0625, + "sans": 144.015625, + "serif": 147.5625, + "system": 146.09375 + }, + "platform": "MacIntel", + "local_storage": true, + "session_storage": true, + "date_time_locale": "en-US", + "audio": 124.04347745512496 + } } \ No newline at end of file From 0d8d1d62cb3e56a11de0398b7e7813feff1e4f94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eray=20Ayd=C4=B1n?= Date: Wed, 25 Feb 2026 21:23:25 +0300 Subject: [PATCH 62/71] test: add ruleset evaluation smoke tests Validate rule_action response for events matched by a ruleset. Related-Task: INTER-1488 --- tests/functional-tests/.env.example | 1 + tests/functional-tests/smokeTests.mjs | 65 +++++++++++++++++++++++++++ 2 files changed, 66 insertions(+) diff --git a/tests/functional-tests/.env.example b/tests/functional-tests/.env.example index cde8cf20..935ce871 100644 --- a/tests/functional-tests/.env.example +++ b/tests/functional-tests/.env.example @@ -1,3 +1,4 @@ API_KEY= # "eu" or "ap", "us" is the default REGION= +RULESET_ID= diff --git a/tests/functional-tests/smokeTests.mjs b/tests/functional-tests/smokeTests.mjs index 934d46f8..69d905db 100644 --- a/tests/functional-tests/smokeTests.mjs +++ b/tests/functional-tests/smokeTests.mjs @@ -5,10 +5,12 @@ import { TooManyRequestsError, } from '@fingerprint/fingerprint-server-sdk' import { config } from 'dotenv' +import assert from "node:assert"; config() const REGION_MAP = { eu: Region.EU, ap: Region.AP, us: Region.Global } const region = REGION_MAP[(process.env.REGION ?? 'us').toLowerCase()] ?? Region.Global +const ruleset_id = process.env.RULESET_ID function createClient() { if (!process.env['API_KEY']) { @@ -54,6 +56,64 @@ async function fetchEventAndVisitorDetails(client, firstEvent, start, end) { } } +async function validateRulesetEvaluationForBlock(client, start, end) { + console.log('Trying to find event with `incognito = true`...') + const search = await client.searchEvents({ limit: 1, incognito: true, start, end }) + + if (search.events.length === 0) { + console.warn('No event with `incognito = true`.') + return + } + + const event_id = search.events[0].event_id + const event = await client.getEvent(event_id, { ruleset_id }) + if (!event) { + throw new Error(`Event details are missing for found incognito event`) + } + + const expected = { + ruleset_id, + rule_id: 'r_PMwGXkWtG20KZn', + rule_expression: 'incognito', + type: 'block', + status_code: 403, + headers: [ { name: 'Content-Type', value: 'application/json' } ], + body: '{"message": "Incognito not allowed"}' + } + + assert.deepStrictEqual(event.rule_action, expected) + + console.log('Ruleset evaluation with `block` works!') +} + +async function validateRulesetEvaluationForAllow(client, start, end) { + console.log('Trying to find event with `incognito = false`...') + const search = await client.searchEvents({ limit: 1, incognito: false, start, end }) + + if (search.events.length === 0) { + console.warn('No event with `incognito = false`.') + return + } + + const event_id = search.events[0].event_id + const event = await client.getEvent(event_id, { ruleset_id }) + if (!event) { + throw new Error(`Event details are missing for found non-incognito event`) + } + + const expected = { + ruleset_id, + rule_id: 'r_OPnYaU9dKEke9X', + rule_expression: 'environment_id != "non-an-environment-id"', + type: 'allow', + request_header_modifications: { remove: [], set: [ { name: 'X-Allowed', value: 'true' } ], append: [] } + } + + assert.deepStrictEqual(event.rule_action, expected) + + console.log('Ruleset evaluation with `allow` works!') +} + async function validateOldestOrder(client, start, end) { console.log('Retrieving old 2 events...') const old = await client.searchEvents({ limit: 2, start, end, reverse: true }) @@ -85,6 +145,11 @@ async function main() { await validateOldestOrder(client, start, end) + if (ruleset_id) { + await validateRulesetEvaluationForBlock(client, start, end) + await validateRulesetEvaluationForAllow(client, start, end) + } + console.log('All tests passed') return 0 } catch (error) { From 167e25c0f010ea7c345ccc34ee7c353be1d9253b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eray=20Ayd=C4=B1n?= Date: Thu, 26 Feb 2026 15:37:54 +0300 Subject: [PATCH 63/71] refactor: remove retryAfter refs from docs tests Update updateEvent JSDoc to reflect the new one month retention limit. Remove TooManyRequestsError.retryAfter usage from docs and tests. Related-Task: INTER-1488 --- src/serverApiClient.ts | 6 +----- tests/functional-tests/smokeTests.mjs | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/serverApiClient.ts b/src/serverApiClient.ts index 921df27d..6acc8788 100644 --- a/src/serverApiClient.ts +++ b/src/serverApiClient.ts @@ -106,7 +106,7 @@ export class FingerprintJsServerApiClient implements FingerprintApi { * * When an event is created, it is assigned `linkedId` and `tag` submitted through the JS agent parameters. This information might not be available on the client so the Server API allows for updating the attributes after the fact. * - * **Warning** It's not possible to update events older than 10 days. + * **Warning** It's not possible to update events older than one month. * * @param body - Data to update the event with. * @param eventId The unique event [identifier](https://docs.fingerprint.com/reference/js-agent-v4-get-function#event_id). @@ -177,10 +177,6 @@ export class FingerprintJsServerApiClient implements FingerprintApi { * console.log(error.statusCode, error.message) * // Access raw response in error * console.log(error.response) - * - * if(error instanceof TooManyRequestsError) { - * retryLater(error.retryAfter) // Needs to be implemented on your side - * } * } * }) * ``` diff --git a/tests/functional-tests/smokeTests.mjs b/tests/functional-tests/smokeTests.mjs index 69d905db..c30c4293 100644 --- a/tests/functional-tests/smokeTests.mjs +++ b/tests/functional-tests/smokeTests.mjs @@ -154,7 +154,7 @@ async function main() { return 0 } catch (error) { if (error instanceof TooManyRequestsError) { - console.error(`[TooManyRequestsError]: Rate limited. Retry after: ${error.retryAfter}s`) + console.error('[TooManyRequestsError]: Rate limited') return 1 } From 3c7455a0580db905a81392ad162529b44630531c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eray=20Ayd=C4=B1n?= Date: Thu, 26 Feb 2026 16:55:35 +0300 Subject: [PATCH 64/71] fix: use repeated keys instead of bracket notation Serialize array query parameters as `key=a&key=b`. Related-Task: INTER-1488 --- src/urlUtils.ts | 2 +- tests/mocked-responses-tests/searchEventsTests.spec.ts | 2 +- tests/unit-tests/urlUtilsTests.spec.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/urlUtils.ts b/src/urlUtils.ts index 27f53d70..6cb3848e 100644 --- a/src/urlUtils.ts +++ b/src/urlUtils.ts @@ -32,7 +32,7 @@ function serializeQueryStringParams(params: QueryStringParameters): string { if (v == null) { continue } - entries.push([`${key}[]`, String(v)]) + entries.push([key, String(v)]) } } else { entries.push([key, String(value)]) diff --git a/tests/mocked-responses-tests/searchEventsTests.spec.ts b/tests/mocked-responses-tests/searchEventsTests.spec.ts index 5a83c026..768c021c 100644 --- a/tests/mocked-responses-tests/searchEventsTests.spec.ts +++ b/tests/mocked-responses-tests/searchEventsTests.spec.ts @@ -125,7 +125,7 @@ describe('[Mocked response] Search Events', () => { if (Array.isArray(value)) { for (const v of value) { - queryParams.append(`${key}[]`, String(v)) + queryParams.append(key, String(v)) } } else { queryParams.set(key, String(value)) diff --git a/tests/unit-tests/urlUtilsTests.spec.ts b/tests/unit-tests/urlUtilsTests.spec.ts index 7f7804df..59c7dee2 100644 --- a/tests/unit-tests/urlUtilsTests.spec.ts +++ b/tests/unit-tests/urlUtilsTests.spec.ts @@ -158,7 +158,7 @@ describe('getRequestPath', () => { }, }) - const expected = `https://api.fpjs.io/v4/events?environment%5B%5D=a&environment%5B%5D=b&${ii}` + const expected = `https://api.fpjs.io/v4/events?environment=a&environment=b&${ii}` expect(actual).toEqual(expected) }) From 1ff98564ad60191b0d33957424eb72035dddf917 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eray=20Ayd=C4=B1n?= Date: Thu, 26 Feb 2026 18:10:34 +0300 Subject: [PATCH 65/71] fix: throw on unexpected non-JSON content return Separate 204 No Content handling from non-JSON content type detection. Related-Task: INTER-1488 --- src/serverApiClient.ts | 9 ++++++++- .../deleteVisitorDataTests.spec.ts | 7 ++++++- .../updateEventTests.spec.ts | 7 ++++++- tests/unit-tests/serverApiClientTests.spec.ts | 14 +++++++++++--- 4 files changed, 31 insertions(+), 6 deletions(-) diff --git a/src/serverApiClient.ts b/src/serverApiClient.ts index 6acc8788..f382bd3c 100644 --- a/src/serverApiClient.ts +++ b/src/serverApiClient.ts @@ -285,9 +285,16 @@ export class FingerprintJsServerApiClient implements FingerprintApi { const isJson = contentType.includes('application/json') if (response.ok) { - if (!isJson || response.status === 204) { + const hasNoBody = response.status === 204 || response.headers.get('content-length') === '0' + + if (hasNoBody) { return undefined as SuccessJsonOrVoid } + + if (!isJson) { + throw new SdkError('Expected JSON response but received non-JSON content type', response) + } + let data try { data = await response.clone().json() diff --git a/tests/mocked-responses-tests/deleteVisitorDataTests.spec.ts b/tests/mocked-responses-tests/deleteVisitorDataTests.spec.ts index dbc37d9f..5fb3f93f 100644 --- a/tests/mocked-responses-tests/deleteVisitorDataTests.spec.ts +++ b/tests/mocked-responses-tests/deleteVisitorDataTests.spec.ts @@ -24,7 +24,7 @@ describe('[Mocked response] Delete visitor data', () => { const client = new FingerprintJsServerApiClient({ region: Region.EU, apiKey }) test('with visitorId', async () => { - mockFetch.mockReturnValue(Promise.resolve(new Response())) + mockFetch.mockReturnValue(Promise.resolve(new Response(undefined, { headers: { 'content-length': '0' } }))) const response = await client.deleteVisitorData(existingVisitorId) @@ -41,6 +41,7 @@ describe('[Mocked response] Delete visitor data', () => { test('404 error', async () => { const mockResponse = new Response(JSON.stringify(Error404), { status: 404, + headers: { 'content-type': 'application/json' }, }) mockFetch.mockReturnValue(Promise.resolve(mockResponse)) @@ -52,6 +53,7 @@ describe('[Mocked response] Delete visitor data', () => { test('403 error', async () => { const mockResponse = new Response(JSON.stringify(Error403), { status: 403, + headers: { 'content-type': 'application/json' }, }) mockFetch.mockReturnValue(Promise.resolve(mockResponse)) @@ -63,6 +65,7 @@ describe('[Mocked response] Delete visitor data', () => { test('400 error', async () => { const mockResponse = new Response(JSON.stringify(Error400), { status: 400, + headers: { 'content-type': 'application/json' }, }) mockFetch.mockReturnValue(Promise.resolve(mockResponse)) @@ -74,6 +77,7 @@ describe('[Mocked response] Delete visitor data', () => { test('429 error', async () => { const mockResponse = new Response(JSON.stringify(Error429), { status: 429, + headers: { 'content-type': 'application/json' }, }) mockFetch.mockReturnValue(Promise.resolve(mockResponse)) @@ -104,6 +108,7 @@ describe('[Mocked response] Delete visitor data', () => { }), { status: 404, + headers: { 'content-type': 'application/json' }, } ) diff --git a/tests/mocked-responses-tests/updateEventTests.spec.ts b/tests/mocked-responses-tests/updateEventTests.spec.ts index 9dc89523..88869eaf 100644 --- a/tests/mocked-responses-tests/updateEventTests.spec.ts +++ b/tests/mocked-responses-tests/updateEventTests.spec.ts @@ -23,7 +23,7 @@ describe('[Mocked response] Update event', () => { const client = new FingerprintJsServerApiClient({ region: Region.EU, apiKey }) test('with eventId', async () => { - mockFetch.mockReturnValue(Promise.resolve(new Response())) + mockFetch.mockReturnValue(Promise.resolve(new Response(undefined, { headers: { 'content-length': '0' } }))) const body = { linked_id: 'linked_id', @@ -50,6 +50,7 @@ describe('[Mocked response] Update event', () => { test('404 error', async () => { const mockResponse = new Response(JSON.stringify(Error404), { status: 404, + headers: { 'content-type': 'application/json' }, }) mockFetch.mockReturnValue(Promise.resolve(mockResponse)) @@ -65,6 +66,7 @@ describe('[Mocked response] Update event', () => { test('403 error', async () => { const mockResponse = new Response(JSON.stringify(Error403), { status: 403, + headers: { 'content-type': 'application/json' }, }) mockFetch.mockReturnValue(Promise.resolve(mockResponse)) @@ -80,6 +82,7 @@ describe('[Mocked response] Update event', () => { test('400 error', async () => { const mockResponse = new Response(JSON.stringify(Error400), { status: 400, + headers: { 'content-type': 'application/json' }, }) mockFetch.mockReturnValue(Promise.resolve(mockResponse)) @@ -95,6 +98,7 @@ describe('[Mocked response] Update event', () => { test('409 error', async () => { const mockResponse = new Response(JSON.stringify(Error409), { status: 409, + headers: { 'content-type': 'application/json' }, }) mockFetch.mockReturnValue(Promise.resolve(mockResponse)) @@ -133,6 +137,7 @@ describe('[Mocked response] Update event', () => { }), { status: 404, + headers: { 'content-type': 'application/json' }, } ) diff --git a/tests/unit-tests/serverApiClientTests.spec.ts b/tests/unit-tests/serverApiClientTests.spec.ts index c98d2efa..52db9057 100644 --- a/tests/unit-tests/serverApiClientTests.spec.ts +++ b/tests/unit-tests/serverApiClientTests.spec.ts @@ -8,7 +8,9 @@ describe('ServerApiClient', () => { }) it('should support passing custom fetch implementation', async () => { - const mockFetch = jest.fn().mockResolvedValue(new Response(JSON.stringify({}))) + const mockFetch = jest + .fn() + .mockResolvedValue(new Response(JSON.stringify({}), { headers: { 'content-type': 'application/json' } })) const client = new FingerprintJsServerApiClient({ fetch: mockFetch, @@ -29,7 +31,11 @@ describe('ServerApiClient', () => { }, } - const mockFetch = jest.fn().mockResolvedValue(new Response(JSON.stringify(responseBody), { status: 403 })) + const mockFetch = jest + .fn() + .mockResolvedValue( + new Response(JSON.stringify(responseBody), { status: 403, headers: { 'content-type': 'application/json' } }) + ) const client = new FingerprintJsServerApiClient({ fetch: mockFetch, @@ -100,7 +106,9 @@ describe('ServerApiClient', () => { }) it('should support using a string constant for Authorization header', async () => { - const mockFetch = jest.fn().mockResolvedValue(new Response(JSON.stringify({}))) + const mockFetch = jest + .fn() + .mockResolvedValue(new Response(JSON.stringify({}), { headers: { 'content-type': 'application/json' } })) const apiKey = 'test' From 200eddf9ab7ab449298febf230d977b490670f4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eray=20Ayd=C4=B1n?= Date: Thu, 26 Feb 2026 18:53:27 +0300 Subject: [PATCH 66/71] refactor!: rename FingerprintJsServerApiClient Rename the main SDK client class from `FingerprintJsServerApiClient` to `FingerprintServerApiClient`. BREAKING CHANGE: `FingerprintJsServerApiClient` has been renamed to `FingerprintServerApiClient`. Related-Task: INTER-1488 --- .changeset/early-seas-look.md | 3 ++- example/deleteVisitor.mjs | 4 ++-- example/getEvent.mjs | 4 ++-- example/searchEvents.mjs | 4 ++-- example/updateEvent.mjs | 4 ++-- readme.md | 12 +++++----- src/serverApiClient.ts | 2 +- tests/functional-tests/smokeTests.mjs | 4 ++-- .../deleteVisitorDataTests.spec.ts | 4 ++-- .../getEventTests.spec.ts | 4 ++-- .../searchEventsTests.spec.ts | 4 ++-- .../updateEventTests.spec.ts | 4 ++-- tests/unit-tests/serverApiClientTests.spec.ts | 24 +++++++++---------- 13 files changed, 39 insertions(+), 38 deletions(-) diff --git a/.changeset/early-seas-look.md b/.changeset/early-seas-look.md index 3f884fc5..4fde6704 100644 --- a/.changeset/early-seas-look.md +++ b/.changeset/early-seas-look.md @@ -5,13 +5,14 @@ **Server APIv3 -> Server APIv4 migration** - Switch all endpoints to `/v4/*`. -- Remove `authenticationMode` option when initializing `FingerprintJsServerApiClient`. +- Remove `authenticationMode` option when initializing `FingerprintServerApiClient`. - Rename `request_id` to `event_id`. - Use snake_case fields when updating an event. - Use `PATCH` method when updating an event. - Examples, tests, and docs updated. **BREAKING CHANGES** +- Use new client when initializing: `FingerprintServerApiClient`. - `authenticationMode` option removed. - Removed `getVisits()` function. - Removed `getRelatedVisitors()` function. diff --git a/example/deleteVisitor.mjs b/example/deleteVisitor.mjs index a5701568..27038522 100644 --- a/example/deleteVisitor.mjs +++ b/example/deleteVisitor.mjs @@ -1,4 +1,4 @@ -import { FingerprintJsServerApiClient, Region, RequestError } from '@fingerprint/fingerprint-server-sdk' +import { FingerprintServerApiClient, Region, RequestError } from '@fingerprint/fingerprint-server-sdk' import { config } from 'dotenv' config() @@ -23,7 +23,7 @@ if (envRegion === 'eu') { region = Region.AP } -const client = new FingerprintJsServerApiClient({ region, apiKey }) +const client = new FingerprintServerApiClient({ region, apiKey }) try { await client.deleteVisitorData(visitorId) diff --git a/example/getEvent.mjs b/example/getEvent.mjs index 71114987..e529cb94 100644 --- a/example/getEvent.mjs +++ b/example/getEvent.mjs @@ -1,4 +1,4 @@ -import { FingerprintJsServerApiClient, Region, RequestError } from '@fingerprint/fingerprint-server-sdk' +import { FingerprintServerApiClient, Region, RequestError } from '@fingerprint/fingerprint-server-sdk' import { config } from 'dotenv' config() @@ -24,7 +24,7 @@ if (envRegion === 'eu') { region = Region.AP } -const client = new FingerprintJsServerApiClient({ region, apiKey }) +const client = new FingerprintServerApiClient({ region, apiKey }) try { const event = await client.getEvent(eventId, { ruleset_id: rulesetId }) diff --git a/example/searchEvents.mjs b/example/searchEvents.mjs index 03c6016a..d71362f1 100644 --- a/example/searchEvents.mjs +++ b/example/searchEvents.mjs @@ -1,4 +1,4 @@ -import { FingerprintJsServerApiClient, Region, RequestError } from '@fingerprint/fingerprint-server-sdk' +import { FingerprintServerApiClient, Region, RequestError } from '@fingerprint/fingerprint-server-sdk' import { config } from 'dotenv' config() @@ -17,7 +17,7 @@ if (envRegion === 'eu') { region = Region.AP } -const client = new FingerprintJsServerApiClient({ region, apiKey }) +const client = new FingerprintServerApiClient({ region, apiKey }) const filter = { limit: 10, diff --git a/example/updateEvent.mjs b/example/updateEvent.mjs index 07ae091a..8bd0f7d9 100644 --- a/example/updateEvent.mjs +++ b/example/updateEvent.mjs @@ -1,4 +1,4 @@ -import { FingerprintJsServerApiClient, RequestError, Region } from '@fingerprint/fingerprint-server-sdk' +import { FingerprintServerApiClient, RequestError, Region } from '@fingerprint/fingerprint-server-sdk' import { config } from 'dotenv' config() @@ -24,7 +24,7 @@ if (envRegion === 'eu') { region = Region.AP } -const client = new FingerprintJsServerApiClient({ region, apiKey }) +const client = new FingerprintServerApiClient({ region, apiKey }) try { await client.updateEvent( diff --git a/readme.md b/readme.md index 866ce3d6..3c32caa9 100644 --- a/readme.md +++ b/readme.md @@ -39,7 +39,7 @@ Supported runtimes: To make it work, replace the SDK's built-in `fetch` function (which relies on Node APIs) with the runtime's native `fetch` function. Pass the function into the constructor with proper binding: ```js - const client = new FingerprintJsServerApiClient({ + const client = new FingerprintServerApiClient({ region: Region.EU, apiKey: apiKey, fetch: fetch.bind(globalThis), @@ -73,11 +73,11 @@ Initialize the client instance and use it to make API requests. You need to spec ```ts import { - FingerprintJsServerApiClient, + FingerprintServerApiClient, Region, } from '@fingerprint/fingerprint-server-sdk' -const client = new FingerprintJsServerApiClient({ +const client = new FingerprintServerApiClient({ apiKey: '', region: Region.Global, }) @@ -119,11 +119,11 @@ When handling errors, you can check for it like this: ```typescript import { RequestError, - FingerprintJsServerApiClient, + FingerprintServerApiClient, TooManyRequestsError, } from '@fingerprint/fingerprint-server-sdk' -const client = new FingerprintJsServerApiClient({ +const client = new FingerprintServerApiClient({ apiKey: '', region: Region.Global, }) @@ -170,7 +170,7 @@ To learn more, see [example/unsealResult.mjs](https://github.com/fingerprintjs/n ### Deleting visitor data -Customers on the Enterprise plan can [Delete all data associated with a specific visitor](https://dev.fingerprint.com/reference/deletevisitordata) to comply with privacy regulations. See [example/deleteVisitor.mjs](https://github.com/fingerprintjs/node-sdk/tree/main/example/deleteVisitor.mjs) or the [API Reference](https://fingerprintjs.github.io/node-sdk/classes/FingerprintJsServerApiClient.html#deleteVisitorData). +Customers on the Enterprise plan can [Delete all data associated with a specific visitor](https://dev.fingerprint.com/reference/deletevisitordata) to comply with privacy regulations. See [example/deleteVisitor.mjs](https://github.com/fingerprintjs/node-sdk/tree/main/example/deleteVisitor.mjs) or the [API Reference](https://fingerprintjs.github.io/node-sdk/classes/FingerprintServerApiClient.html#deleteVisitorData). ## API Reference diff --git a/src/serverApiClient.ts b/src/serverApiClient.ts index f382bd3c..a2ccc7b0 100644 --- a/src/serverApiClient.ts +++ b/src/serverApiClient.ts @@ -14,7 +14,7 @@ import { RequestError, SdkError, TooManyRequestsError } from './errors/apiErrors import { isErrorResponse } from './errors/handleErrorResponse' import { toError } from './errors/toError' -export class FingerprintJsServerApiClient implements FingerprintApi { +export class FingerprintServerApiClient implements FingerprintApi { public readonly region: Region public readonly apiKey: string diff --git a/tests/functional-tests/smokeTests.mjs b/tests/functional-tests/smokeTests.mjs index c30c4293..88c31981 100644 --- a/tests/functional-tests/smokeTests.mjs +++ b/tests/functional-tests/smokeTests.mjs @@ -1,5 +1,5 @@ import { - FingerprintJsServerApiClient, + FingerprintServerApiClient, Region, RequestError, TooManyRequestsError, @@ -17,7 +17,7 @@ function createClient() { throw new Error('Missing required API_KEY env variable!') } - return new FingerprintJsServerApiClient({ region, apiKey: process.env.API_KEY }) + return new FingerprintServerApiClient({ region, apiKey: process.env.API_KEY }) } async function getRecentEvents(client, start, end) { diff --git a/tests/mocked-responses-tests/deleteVisitorDataTests.spec.ts b/tests/mocked-responses-tests/deleteVisitorDataTests.spec.ts index 5fb3f93f..07a20dbd 100644 --- a/tests/mocked-responses-tests/deleteVisitorDataTests.spec.ts +++ b/tests/mocked-responses-tests/deleteVisitorDataTests.spec.ts @@ -1,6 +1,6 @@ import { ErrorResponse, - FingerprintJsServerApiClient, + FingerprintServerApiClient, getIntegrationInfo, Region, RequestError, @@ -21,7 +21,7 @@ describe('[Mocked response] Delete visitor data', () => { const existingVisitorId = 'TaDnMBz9XCpZNuSzFUqP' - const client = new FingerprintJsServerApiClient({ region: Region.EU, apiKey }) + const client = new FingerprintServerApiClient({ region: Region.EU, apiKey }) test('with visitorId', async () => { mockFetch.mockReturnValue(Promise.resolve(new Response(undefined, { headers: { 'content-length': '0' } }))) diff --git a/tests/mocked-responses-tests/getEventTests.spec.ts b/tests/mocked-responses-tests/getEventTests.spec.ts index 251391e4..4aea02b3 100644 --- a/tests/mocked-responses-tests/getEventTests.spec.ts +++ b/tests/mocked-responses-tests/getEventTests.spec.ts @@ -1,6 +1,6 @@ import { ErrorResponse, - FingerprintJsServerApiClient, + FingerprintServerApiClient, getIntegrationInfo, Region, RequestError, @@ -18,7 +18,7 @@ describe('[Mocked response] Get Event', () => { const existingEventId = '1626550679751.cVc5Pm' const rulesetId = 'rs_b1k1blhqpOX3kU' - const client = new FingerprintJsServerApiClient({ region: Region.EU, apiKey }) + const client = new FingerprintServerApiClient({ region: Region.EU, apiKey }) test('with event_id', async () => { mockFetch.mockReturnValue(Promise.resolve(createJsonResponse(getEventResponse))) diff --git a/tests/mocked-responses-tests/searchEventsTests.spec.ts b/tests/mocked-responses-tests/searchEventsTests.spec.ts index 768c021c..fdd730ad 100644 --- a/tests/mocked-responses-tests/searchEventsTests.spec.ts +++ b/tests/mocked-responses-tests/searchEventsTests.spec.ts @@ -1,6 +1,6 @@ import { ErrorResponse, - FingerprintJsServerApiClient, + FingerprintServerApiClient, getIntegrationInfo, RequestError, SearchEventsFilter, @@ -14,7 +14,7 @@ const mockFetch = fetch as unknown as jest.Mock describe('[Mocked response] Search Events', () => { const apiKey = 'dummy_api_key' - const client = new FingerprintJsServerApiClient({ apiKey }) + const client = new FingerprintServerApiClient({ apiKey }) test('without filter', async () => { mockFetch.mockReturnValue(Promise.resolve(createJsonResponse(getEventsSearch))) diff --git a/tests/mocked-responses-tests/updateEventTests.spec.ts b/tests/mocked-responses-tests/updateEventTests.spec.ts index 88869eaf..217ff9e9 100644 --- a/tests/mocked-responses-tests/updateEventTests.spec.ts +++ b/tests/mocked-responses-tests/updateEventTests.spec.ts @@ -1,6 +1,6 @@ import { ErrorResponse, - FingerprintJsServerApiClient, + FingerprintServerApiClient, getIntegrationInfo, Region, RequestError, @@ -20,7 +20,7 @@ describe('[Mocked response] Update event', () => { const existingEventId = 'TaDnMBz9XCpZNuSzFUqP' - const client = new FingerprintJsServerApiClient({ region: Region.EU, apiKey }) + const client = new FingerprintServerApiClient({ region: Region.EU, apiKey }) test('with eventId', async () => { mockFetch.mockReturnValue(Promise.resolve(new Response(undefined, { headers: { 'content-length': '0' } }))) diff --git a/tests/unit-tests/serverApiClientTests.spec.ts b/tests/unit-tests/serverApiClientTests.spec.ts index 52db9057..f8f440a2 100644 --- a/tests/unit-tests/serverApiClientTests.spec.ts +++ b/tests/unit-tests/serverApiClientTests.spec.ts @@ -1,9 +1,9 @@ -import { RequestError, FingerprintJsServerApiClient, Region, Options, EventUpdate, SdkError } from '../../src' +import { RequestError, FingerprintServerApiClient, Region, Options, EventUpdate, SdkError } from '../../src' describe('ServerApiClient', () => { it('should throw error if no token provided', async () => { expect(() => { - new FingerprintJsServerApiClient({} as Readonly) + new FingerprintServerApiClient({} as Readonly) }).toThrow('Api key is not set') }) @@ -12,7 +12,7 @@ describe('ServerApiClient', () => { .fn() .mockResolvedValue(new Response(JSON.stringify({}), { headers: { 'content-type': 'application/json' } })) - const client = new FingerprintJsServerApiClient({ + const client = new FingerprintServerApiClient({ fetch: mockFetch, apiKey: 'test', region: Region.Global, @@ -37,7 +37,7 @@ describe('ServerApiClient', () => { new Response(JSON.stringify(responseBody), { status: 403, headers: { 'content-type': 'application/json' } }) ) - const client = new FingerprintJsServerApiClient({ + const client = new FingerprintServerApiClient({ fetch: mockFetch, apiKey: 'test', region: Region.Global, @@ -60,7 +60,7 @@ describe('ServerApiClient', () => { }) it('should support using a string constant for the Region', () => { - const client = new FingerprintJsServerApiClient({ + const client = new FingerprintServerApiClient({ apiKey: 'test', region: 'Global', }) @@ -71,7 +71,7 @@ describe('ServerApiClient', () => { }) it('should throw error when using getEvent if eventId is empty', async () => { - const client = new FingerprintJsServerApiClient({ + const client = new FingerprintServerApiClient({ apiKey: 'test', region: 'Global', }) @@ -80,7 +80,7 @@ describe('ServerApiClient', () => { }) it('should throw error when using updateEvent if body or eventId is empty', async () => { - const client = new FingerprintJsServerApiClient({ + const client = new FingerprintServerApiClient({ apiKey: 'test', region: 'Global', }) @@ -95,7 +95,7 @@ describe('ServerApiClient', () => { }) it('should throw error when using deleteVisitorData if visitorId is empty', async () => { - const client = new FingerprintJsServerApiClient({ + const client = new FingerprintServerApiClient({ apiKey: 'test', region: 'Global', }) @@ -112,7 +112,7 @@ describe('ServerApiClient', () => { const apiKey = 'test' - const client = new FingerprintJsServerApiClient({ + const client = new FingerprintServerApiClient({ fetch: mockFetch, apiKey, region: Region.Global, @@ -132,7 +132,7 @@ describe('ServerApiClient', () => { it('should throw SdkError if fetch fails', async () => { const mockFetch = jest.fn().mockRejectedValue(new Error('fetch error')) - const client = new FingerprintJsServerApiClient({ + const client = new FingerprintServerApiClient({ fetch: mockFetch, apiKey: 'test', }) @@ -152,7 +152,7 @@ describe('ServerApiClient', () => { const mockFetch = jest.fn().mockResolvedValue(badJsonOk as unknown as Response) - const client = new FingerprintJsServerApiClient({ + const client = new FingerprintServerApiClient({ fetch: mockFetch, apiKey: 'test', }) @@ -177,7 +177,7 @@ describe('ServerApiClient', () => { const mockFetch = jest.fn().mockResolvedValue(badJsonFail as unknown as Response) - const client = new FingerprintJsServerApiClient({ + const client = new FingerprintServerApiClient({ fetch: mockFetch, apiKey: 'test', }) From 5fd1884ffdc51e8bcde51a167b8a8a82749f6823 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eray=20Ayd=C4=B1n?= Date: Thu, 26 Feb 2026 19:10:48 +0300 Subject: [PATCH 67/71] fix: check error response shape before throw 429 Move the 429 status check inside the isErrorResponse guard. Related-Task: INTER-1488 --- src/serverApiClient.ts | 6 +++--- .../mocked-responses-tests/getEventTests.spec.ts | 15 +++++++++++++++ 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/serverApiClient.ts b/src/serverApiClient.ts index a2ccc7b0..5799c742 100644 --- a/src/serverApiClient.ts +++ b/src/serverApiClient.ts @@ -310,10 +310,10 @@ export class FingerprintServerApiClient implements FingerprintApi { } catch (e) { throw new SdkError('Failed to parse JSON response', response, toError(e)) } - if (response.status === 429) { - throw new TooManyRequestsError(errPayload, response) - } if (isErrorResponse(errPayload)) { + if (response.status === 429) { + throw new TooManyRequestsError(errPayload, response) + } throw new RequestError(errPayload.error.message, errPayload, response.status, errPayload.error.code, response) } throw RequestError.unknown(response) diff --git a/tests/mocked-responses-tests/getEventTests.spec.ts b/tests/mocked-responses-tests/getEventTests.spec.ts index 4aea02b3..a823aefb 100644 --- a/tests/mocked-responses-tests/getEventTests.spec.ts +++ b/tests/mocked-responses-tests/getEventTests.spec.ts @@ -5,9 +5,11 @@ import { Region, RequestError, SdkError, + TooManyRequestsError, } from '../../src' import getEventResponse from './mocked-responses-data/events/get_event_200.json' import getEventRulesetResponse from './mocked-responses-data/events/get_event_ruleset_200.json' +import Error429 from './mocked-responses-data/errors/429_too_many_requests.json' import { createJsonResponse } from './utils' jest.spyOn(global, 'fetch') @@ -90,6 +92,19 @@ describe('[Mocked response] Get Event', () => { await expect(client.getEvent(existingEventId)).rejects.toThrow('Unknown error') }) + test('429 error with valid shape', async () => { + const mockResponse = createJsonResponse(Error429, 429) + mockFetch.mockReturnValue(Promise.resolve(mockResponse)) + await expect(client.getEvent(existingEventId)).rejects.toBeInstanceOf(TooManyRequestsError) + }) + + test('429 error with invalid shape', async () => { + const mockResponse = createJsonResponse({ reason: 'rate limited' }, 429) + mockFetch.mockReturnValue(Promise.resolve(mockResponse)) + await expect(client.getEvent(existingEventId)).rejects.toBeInstanceOf(RequestError) + await expect(client.getEvent(existingEventId)).rejects.not.toBeInstanceOf(TooManyRequestsError) + }) + test('Error with bad JSON', async () => { const mockResponse = new Response('(Some bad JSON)', { status: 404, From 61cc2c46255f211364c06292b7d8545404741820 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eray=20Ayd=C4=B1n?= Date: Thu, 26 Feb 2026 19:21:03 +0300 Subject: [PATCH 68/71] chore: fix changeset message for getEvent --- .changeset/witty-lobsters-film.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/witty-lobsters-film.md b/.changeset/witty-lobsters-film.md index 841be6b0..73e90628 100644 --- a/.changeset/witty-lobsters-film.md +++ b/.changeset/witty-lobsters-film.md @@ -2,4 +2,4 @@ '@fingerprint/fingerprint-server-sdk': minor --- -add `rulesetId` parameter to the `getEvent` operation +add `options` parameter to the `getEvent` operation From 3a59fb571144c5c0a91b3c5ff4e0caa0c3d1dcf1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eray=20Ayd=C4=B1n?= Date: Thu, 26 Feb 2026 20:54:49 +0300 Subject: [PATCH 69/71] refactor: dont export urlUtils from public API Remove `export * from './urlUtils'` from the package entry point to keep internal helpers out of the public API surface. Related-Task: INTER-1488 --- src/index.ts | 1 - .../deleteVisitorDataTests.spec.ts | 2 +- tests/mocked-responses-tests/getEventTests.spec.ts | 2 +- tests/mocked-responses-tests/searchEventsTests.spec.ts | 9 ++------- tests/mocked-responses-tests/updateEventTests.spec.ts | 10 ++-------- tests/unit-tests/urlUtilsTests.spec.ts | 3 ++- 6 files changed, 8 insertions(+), 19 deletions(-) diff --git a/src/index.ts b/src/index.ts index 8c4573b4..c8fec968 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,3 @@ -export * from './urlUtils' export * from './serverApiClient' export * from './types' export * from './sealedResults' diff --git a/tests/mocked-responses-tests/deleteVisitorDataTests.spec.ts b/tests/mocked-responses-tests/deleteVisitorDataTests.spec.ts index 07a20dbd..e112a7cc 100644 --- a/tests/mocked-responses-tests/deleteVisitorDataTests.spec.ts +++ b/tests/mocked-responses-tests/deleteVisitorDataTests.spec.ts @@ -1,7 +1,6 @@ import { ErrorResponse, FingerprintServerApiClient, - getIntegrationInfo, Region, RequestError, SdkError, @@ -11,6 +10,7 @@ import Error404 from './mocked-responses-data/errors/404_visitor_not_found.json' import Error403 from './mocked-responses-data/errors/403_feature_not_enabled.json' import Error400 from './mocked-responses-data/errors/400_visitor_id_invalid.json' import Error429 from './mocked-responses-data/errors/429_too_many_requests.json' +import { getIntegrationInfo } from '../../src/urlUtils' jest.spyOn(global, 'fetch') diff --git a/tests/mocked-responses-tests/getEventTests.spec.ts b/tests/mocked-responses-tests/getEventTests.spec.ts index a823aefb..f777603e 100644 --- a/tests/mocked-responses-tests/getEventTests.spec.ts +++ b/tests/mocked-responses-tests/getEventTests.spec.ts @@ -1,7 +1,6 @@ import { ErrorResponse, FingerprintServerApiClient, - getIntegrationInfo, Region, RequestError, SdkError, @@ -11,6 +10,7 @@ import getEventResponse from './mocked-responses-data/events/get_event_200.json' import getEventRulesetResponse from './mocked-responses-data/events/get_event_ruleset_200.json' import Error429 from './mocked-responses-data/errors/429_too_many_requests.json' import { createJsonResponse } from './utils' +import { getIntegrationInfo } from '../../src/urlUtils' jest.spyOn(global, 'fetch') diff --git a/tests/mocked-responses-tests/searchEventsTests.spec.ts b/tests/mocked-responses-tests/searchEventsTests.spec.ts index fdd730ad..ef96be86 100644 --- a/tests/mocked-responses-tests/searchEventsTests.spec.ts +++ b/tests/mocked-responses-tests/searchEventsTests.spec.ts @@ -1,12 +1,7 @@ -import { - ErrorResponse, - FingerprintServerApiClient, - getIntegrationInfo, - RequestError, - SearchEventsFilter, -} from '../../src' +import { ErrorResponse, FingerprintServerApiClient, RequestError, SearchEventsFilter } from '../../src' import getEventsSearch from './mocked-responses-data/events/search/get_event_search_200.json' import { createJsonResponse } from './utils' +import { getIntegrationInfo } from '../../src/urlUtils' jest.spyOn(global, 'fetch') diff --git a/tests/mocked-responses-tests/updateEventTests.spec.ts b/tests/mocked-responses-tests/updateEventTests.spec.ts index 217ff9e9..ee6e83ac 100644 --- a/tests/mocked-responses-tests/updateEventTests.spec.ts +++ b/tests/mocked-responses-tests/updateEventTests.spec.ts @@ -1,15 +1,9 @@ -import { - ErrorResponse, - FingerprintServerApiClient, - getIntegrationInfo, - Region, - RequestError, - SdkError, -} from '../../src' +import { ErrorResponse, FingerprintServerApiClient, Region, RequestError, SdkError } from '../../src' import Error404 from './mocked-responses-data/errors/404_event_not_found.json' import Error403 from './mocked-responses-data/errors/403_feature_not_enabled.json' import Error400 from './mocked-responses-data/errors/400_request_body_invalid.json' import Error409 from './mocked-responses-data/errors/409_state_not_ready.json' +import { getIntegrationInfo } from '../../src/urlUtils' jest.spyOn(global, 'fetch') diff --git a/tests/unit-tests/urlUtilsTests.spec.ts b/tests/unit-tests/urlUtilsTests.spec.ts index 59c7dee2..490a65f9 100644 --- a/tests/unit-tests/urlUtilsTests.spec.ts +++ b/tests/unit-tests/urlUtilsTests.spec.ts @@ -1,5 +1,6 @@ -import { Region, getRequestPath, SearchEventsFilter } from '../../src' +import { Region, SearchEventsFilter } from '../../src' import { version } from '../../package.json' +import { getRequestPath } from '../../src/urlUtils' const visitorId = 'TaDnMBz9XCpZNuSzFUqP' const eventId = '1626550679751.cVc5Pm' From c9c554259a2b0a4efe9168d89bc2566079eebe7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eray=20Ayd=C4=B1n?= Date: Thu, 26 Feb 2026 21:08:23 +0300 Subject: [PATCH 70/71] docs: update contributing markdown file --- contributing.md | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/contributing.md b/contributing.md index bc7a70ac..3c3459b8 100644 --- a/contributing.md +++ b/contributing.md @@ -84,19 +84,11 @@ Use the `example` folder to make API requests using the local version of the SDK cd example pnpm install node getEvent.mjs - node getVisitorHistory.mjs + node searchEvents.mjs ``` Every time you change the SDK code, you need to rebuild it in the root folder using `pnpm build` and then run the example again. ### How to publish -The library is automatically released and published to NPM on every push to the main branch if there are relevant changes using [semantic-release](https://github.com/semantic-release/semantic-release) with following plugins: - -- [@semantic-release/commit-analyzer](https://github.com/semantic-release/commit-analyzer) -- [@semantic-release/release-notes-generator](https://github.com/semantic-release/release-notes-generator) -- [@semantic-release/changelog](https://github.com/semantic-release/changelog) -- [@semantic-release/npm](https://github.com/semantic-release/npm) -- [@semantic-release/github](https://github.com/semantic-release/github) - -The workflow must be approved by one of the maintainers, first. +We use [changesets](https://github.com/changesets/changesets) for handling release notes. If there are relevant changes, please add them to changeset via `pnpm exec changeset`. You need to run `pnpm install` before doing so. From 2f1b7e037ee54d6b39e5d9f5f205f6949f0f870e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eray=20Ayd=C4=B1n?= Date: Thu, 26 Feb 2026 21:18:43 +0300 Subject: [PATCH 71/71] refactor: use explicit named exports for types Replace wildcard re-export of types with explicit named exports. Related-Task: INTER-1488 --- src/index.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index c8fec968..d775d44a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,15 @@ export * from './serverApiClient' -export * from './types' +export { + ErrorResponse, + Event, + EventRuleAction, + EventUpdate, + GetEventOptions, + Options, + Region, + SearchEventsFilter, + SearchEventsResponse, +} from './types' export * from './sealedResults' export * from './errors/unsealError' export * from './webhook'