diff --git a/.changeset/good-experts-watch.md b/.changeset/good-experts-watch.md new file mode 100644 index 00000000..fe1580fd --- /dev/null +++ b/.changeset/good-experts-watch.md @@ -0,0 +1,5 @@ +--- +'fingerprint-pro-server-api-openapi': minor +--- + +**events-search**: Add a new `events/search` API endpoint. Allow users to search for identification events matching one or more search criteria, for example, visitor ID, IP address, bot detection result, etc. diff --git a/.changeset/orange-poets-drive.md b/.changeset/orange-poets-drive.md new file mode 100644 index 00000000..27fc4354 --- /dev/null +++ b/.changeset/orange-poets-drive.md @@ -0,0 +1,5 @@ +--- +'fingerprint-pro-server-api-openapi': minor +--- + +**events**: Add a `suspect` field to the `identification` product schema \ No newline at end of file diff --git a/config/scopes.yaml b/config/scopes.yaml index d2926363..09a4392f 100644 --- a/config/scopes.yaml +++ b/config/scopes.yaml @@ -3,6 +3,10 @@ events: methods: - get - put +events-search: + path: /events/search + methods: + - get visitors: path: /visitors/{visitor_id} methods: diff --git a/schemas/components/schemas/Identification.yaml b/schemas/components/schemas/Identification.yaml index 9e601c5a..da177d26 100644 --- a/schemas/components/schemas/Identification.yaml +++ b/schemas/components/schemas/Identification.yaml @@ -33,6 +33,9 @@ properties: 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 diff --git a/schemas/components/schemas/SearchEventsResponse.yaml b/schemas/components/schemas/SearchEventsResponse.yaml new file mode 100644 index 00000000..1e8a621e --- /dev/null +++ b/schemas/components/schemas/SearchEventsResponse.yaml @@ -0,0 +1,18 @@ +type: object +description: >- + Contains a list of all identification events matching the specified search criteria. +additionalProperties: false +properties: + events: + type: array + items: + type: object + description: >- + Device intelligence results for the identification event. + required: + - products + properties: + products: + $ref: Products.yaml + paginationKey: + type: string \ No newline at end of file diff --git a/schemas/fingerprint-server-api-for-sdks.yaml b/schemas/fingerprint-server-api-for-sdks.yaml index ea2daacc..fcf32da1 100644 --- a/schemas/fingerprint-server-api-for-sdks.yaml +++ b/schemas/fingerprint-server-api-for-sdks.yaml @@ -11,10 +11,13 @@ info: tags: - name: Events description: | - Using the Server API you can retrieve information about individual analysis events using the event's `requestId`. + Retrieve or update information about individual events using the event's request ID. + - name: Event Search + description: | + Search for events matching one or more filters. - name: Visitors description: | - Using the Server API you can retrieve visitor history of individual visitors using their `visitorId`. + Retrieve all events of an individual visitor using their visitor ID. - name: Related Visitors description: | Find visitor IDs that originated from a different browser on the same mobile device. @@ -31,12 +34,14 @@ security: paths: /events/{request_id}: $ref: 'paths/events.yaml' + /events/search: + $ref: 'paths/event-search.yaml' /visitors/{visitor_id}: $ref: 'paths/visitors.yaml' - /webhook: - $ref: 'paths/webhook.yaml' /related-visitors: $ref: './paths/related-visitors.yaml' + /webhook: + $ref: 'paths/webhook.yaml' components: securitySchemes: ApiKeyHeader: diff --git a/schemas/fingerprint-server-api-readme-explorer.yaml b/schemas/fingerprint-server-api-readme-explorer.yaml index b6e9c9e3..73119819 100644 --- a/schemas/fingerprint-server-api-readme-explorer.yaml +++ b/schemas/fingerprint-server-api-readme-explorer.yaml @@ -11,10 +11,13 @@ info: tags: - name: Events description: | - Using the Server API you can retrieve information about individual analysis events using the event's `requestId`. + Retrieve or update information about individual events using the event's `requestId`. + - name: Event Search + description: | + Search for events matching one or more filters. - name: Visitors description: | - Using the Server API you can retrieve visitor history of individual visitors using their `visitorId`. + Retrieve all events of an individual visitor using their `visitorId`. servers: - url: https://api.fpjs.io description: Global @@ -37,6 +40,8 @@ x-readme: paths: /events/{request_id}: $ref: 'paths/events.yaml' + /events/search: + $ref: 'paths/event-search.yaml' /visitors/{visitor_id}: $ref: 'paths/visitors.yaml' components: diff --git a/schemas/paths/event-search.yaml b/schemas/paths/event-search.yaml new file mode 100644 index 00000000..63c57ac5 --- /dev/null +++ b/schemas/paths/event-search.yaml @@ -0,0 +1,145 @@ +get: + tags: + - Event Search + operationId: 'searchEvents' + summary: 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. + + 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. + parameters: + - name: limit + in: query + required: true + schema: + type: integer + format: int32 + minimum: 1 + example: 10 + description: | + Limit the number of events returned. + - name: visitor_id + in: query + schema: + type: string + description: | + Unique [visitor identifier](https://dev.fingerprint.com/reference/get-function#visitorid) issued by Fingerprint Pro. + Filter for events matching this `visitor_id`. + - name: bot + in: query + schema: + type: string + enum: + - all + - good + - bad + - none + description: | + 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. + - 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) + All ip_address filters must use CIDR notation, for example, 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. + + 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. + # example: some_id + - name: start + in: query + schema: + type: integer + format: int64 + description: | + Filter events with a timestamp greater than the start time, in Unix time (milliseconds). + - name: end + in: query + schema: + type: integer + format: int64 + description: | + Filter events with a timestamp smaller than the end time, in Unix time (milliseconds). + - name: reverse + in: query + schema: + type: boolean + description: | + Sort events in reverse timestamp order. + - name: suspect + in: query + schema: + type: boolean + description: | + Filter events previously tagged as suspicious via the [Update API](https://dev.fingerprint.com/reference/updateevent). + responses: + '200': + description: Events matching the filter(s). + content: + application/json: + schema: + $ref: '../components/schemas/SearchEventsResponse.yaml' + examples: + 200-full: + summary: Example search results + externalValue: 'examples/get_event_search_200.json' + '403': + description: Forbidden. Access to this API is denied. + content: + application/json: + schema: + $ref: '../components/schemas/ErrorResponse.yaml' + examples: + 403-token-required: + summary: Error response when the secret API key was not provided. + externalValue: 'examples/errors/403_token_required.json' + 403-token-not-found: + summary: Error response when the provided secret API key does not exist. + externalValue: 'examples/errors/403_token_not_found.json' + 403-wrong-region: + summary: Error response when the API region does not match the region of your Fingerprint workspace. + externalValue: 'examples/errors/403_wrong_region.json' + '400': + description: Bad request. One or more supplied search parameters are invalid, or a required parameter is missing. + content: + application/json: + schema: + $ref: '../components/schemas/ErrorResponse.yaml' + examples: + 400-limit-invalid: + summary: Error response when no limit is supplied, or is invalid. + externalValue: 'examples/errors/400_limit_invalid.json' + 400-ip-address-invalid: + summary: Error response when an invalid IP address is supplied, or is not using CIDR notation. + externalValue: 'examples/errors/400_ip_address_invalid.json' + 400-bot-type-invalid: + summary: Error response when an invalid bot type is specified, must be one of `good`, `bad`, `all`, or `none`. + externalValue: 'examples/errors/400_bot_type_invalid.json' + 400-reverse-invalid: + summary: Error response when the reverse parameter is invalid. + externalValue: 'examples/errors/400_reverse_invalid.json' + 400-start-time-invalid: + summary: Error response when an invalid start time is supplied. + externalValue: 'examples/errors/400_start_time_invalid.json' + 400-end-time-invalid: + summary: Error response when an invalid end time is supplied. + externalValue: 'examples/errors/400_end_time_invalid.json' + 400-visitor-id-invalid: + summary: Error response when an invalid visitor ID is supplied. + externalValue: 'examples/errors/400_visitor_id_invalid.json' + 400-linked-id-invalid: + summary: Error response when an invalid (too large) linked ID is supplied. + externalValue: 'examples/errors/400_linked_id_invalid.json' + 400-pagination-key-invalid: + summary: Error response when an invalid pagination key is supplied. + externalValue: 'examples/errors/400_pagination_key_invalid.json' diff --git a/schemas/paths/examples/errors/400_bot_type_invalid.json b/schemas/paths/examples/errors/400_bot_type_invalid.json new file mode 100644 index 00000000..8dd65266 --- /dev/null +++ b/schemas/paths/examples/errors/400_bot_type_invalid.json @@ -0,0 +1,6 @@ +{ + "error": { + "code": "RequestCannotBeParsed", + "message": "invalid bot type" + } +} diff --git a/schemas/paths/examples/errors/400_end_time_invalid.json b/schemas/paths/examples/errors/400_end_time_invalid.json new file mode 100644 index 00000000..88654093 --- /dev/null +++ b/schemas/paths/examples/errors/400_end_time_invalid.json @@ -0,0 +1,6 @@ +{ + "error": { + "code": "RequestCannotBeParsed", + "message": "invalid end time" + } +} \ No newline at end of file diff --git a/schemas/paths/examples/errors/400_ip_address_invalid.json b/schemas/paths/examples/errors/400_ip_address_invalid.json new file mode 100644 index 00000000..5969bab6 --- /dev/null +++ b/schemas/paths/examples/errors/400_ip_address_invalid.json @@ -0,0 +1,6 @@ +{ + "error": { + "code": "RequestCannotBeParsed", + "message": "invalid ip address" + } +} \ No newline at end of file diff --git a/schemas/paths/examples/errors/400_limit_invalid.json b/schemas/paths/examples/errors/400_limit_invalid.json new file mode 100644 index 00000000..46297eb4 --- /dev/null +++ b/schemas/paths/examples/errors/400_limit_invalid.json @@ -0,0 +1,6 @@ +{ + "error": { + "code": "RequestCannotBeParsed", + "message": "invalid limit" + } +} diff --git a/schemas/paths/examples/errors/400_linked_id_invalid.json b/schemas/paths/examples/errors/400_linked_id_invalid.json new file mode 100644 index 00000000..72de54e0 --- /dev/null +++ b/schemas/paths/examples/errors/400_linked_id_invalid.json @@ -0,0 +1,6 @@ +{ + "error": { + "code": "RequestCannotBeParsed", + "message": "linked_id can't be greater than 256 characters long" + } +} diff --git a/schemas/paths/examples/errors/400_pagination_key_invalid.json b/schemas/paths/examples/errors/400_pagination_key_invalid.json new file mode 100644 index 00000000..df559f9a --- /dev/null +++ b/schemas/paths/examples/errors/400_pagination_key_invalid.json @@ -0,0 +1,6 @@ +{ + "error": { + "code": "RequestCannotBeParsed", + "message": "invalid pagination key" + } +} diff --git a/schemas/paths/examples/errors/400_reverse_invalid.json b/schemas/paths/examples/errors/400_reverse_invalid.json new file mode 100644 index 00000000..540800fa --- /dev/null +++ b/schemas/paths/examples/errors/400_reverse_invalid.json @@ -0,0 +1,6 @@ +{ + "error": { + "code": "RequestCannotBeParsed", + "message": "invalid reverse param" + } +} diff --git a/schemas/paths/examples/errors/400_start_time_invalid.json b/schemas/paths/examples/errors/400_start_time_invalid.json new file mode 100644 index 00000000..5d93f929 --- /dev/null +++ b/schemas/paths/examples/errors/400_start_time_invalid.json @@ -0,0 +1,6 @@ +{ + "error": { + "code": "RequestCannotBeParsed", + "message": "invalid start time" + } +} \ No newline at end of file diff --git a/schemas/paths/examples/get_event_search_200.json b/schemas/paths/examples/get_event_search_200.json new file mode 100644 index 00000000..6cbbd6be --- /dev/null +++ b/schemas/paths/examples/get_event_search_200.json @@ -0,0 +1,340 @@ +{ + "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 + } + } + }, + "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 + } + } + }, + "proxy": { + "data": { + "result": false + } + }, + "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 + } + }, + "remoteControl": { + "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 + } + } + }} + ], + "paginationKey": "1655373953086" +}