GDPR Data Subject Rights APIs¶
(updated: May 5, 2018, 2:00PM Pacific)
With Flurry Analytics in the role of Processor with regards to the data covered by GDPR, it is your responsibility to respond to Data Subject Rights (DSR) requests from your users. This documentation describes the usage of the APIs that Flurry makes available for supporting these DSR requests you receive.
Token Creation¶
As with any Flurry API, in order to use the GDPR APIs, you must first create a Programmatic Token. You can read about how to do that here. The token must be assigned permissions to access Flurry’s GDPR API. To achieve this, after the programmatic account has been created, find the user account on the Admin > Users page and click the pencil icon to edit the user. Then, select Enabled for the GDPR Data Subject Rights API as shown below.

Once you have a Programmatic token that has GDPR API access granted, you can return here to start the integration.
Data Subject Rights¶
There are 4 DSRs to which this API relates. For each of these DSRs, the following actions will be taken for the device specified in the request:
Access - Flurry will return all the data collected
Deletion - Flurry will permanently remove all the data collected. Given the permanence of this action, this request has a built in execution delay of 48 hours.
Restriction - To service a Restriction DSR, you must first download the subject data via an Access request and then issue a Delete request.
Objection - Flurry will permanently remove all the data collected to date and stop the processing of any data sent in the future. As with Deletion, this request has a built in execution delay of 48 hours.
Please Note: In each of the above cases, the APIKey is optional and in its absence data will be returned for all apps to which the Programmatic Token has access.
Other DSRs¶
There are other DSRs for which Flurry does not provide any support.
Rectification - Flurry considers the data collected by your use of Flurry Analytics as “Observed Data”, which is immutable.
API Overview¶
The GDPR API supports the following use cases:
Initiating a request for data subject rights. This process creates a ticket to record the request and can be monitored for progress and status updates.
Querying the status of an existing ticket. Request status has a simple linear transition between the states of ACKNOWLEDGED, PROCESSING, and COMPLETE.
Downloading client data after a request for Access has successfully completed.
Canceling a request for data erasure. Flurry allows request cancellation during the period of time between request submission (ACKNOWLEDGED) and the beginning of request processing (PROCESSING).
Searching for an existing ticket(s) by submission time, device identifier, and other ticket attributes.
The API is accessible at: https://datarequest.flurry.com/gdpr/v1
API Authentication¶
The programmatic token should be included inside an Authorization header for all HTTP requests similar to:
Authorization: Bearer *TOKEN*
*TOKEN* should be replaced with the value of the programmatic token.
Initiating Requests¶
Data subject rights requests to Flurry are initiated by creating a ticket that can be tracked throughout the lifecycle of the request. Developers must provide the following information when creating a ticket:
Ticket Field Name |
Description |
Required/Optional |
---|---|---|
ticketType |
The data subject right requested. It must have a value (case sensitive) of either:
|
Required |
deviceIdType |
|
Required |
deviceId |
The device identifier associated with the data subject right request. |
Required |
apiKey |
If provided, the data subject right request is restricted to the specific project associated with the apiKey. If not provided, the data subject right request is associated with all projects to which the token has access. |
Optional |
HTTP POST Requests are made against the following URL:
curl -g -X POST -H"Content-Type: application/vnd.api+json" -H"Authorization: Bearer $TOKEN" "https://datarequest.flurry.com/gdpr/v1/ticket"
The entity body should specify the required fields:
{
"data": {
"type": "ticket",
"attributes": {
"deviceId": "ABCDEF01-0123-ABCD-ABCD-ABCDEF012345",
"deviceIdType": "IDFV",
"ticketType": "Objection"
}
}
}
When a ticket is generated, the response body includes all attributes from the request including some attributes generated by the API server. These attributes are all permanent members of the ticket and include:
Ticket Field Name |
Description |
---|---|
status |
The current state of the request ticket. Possible values include:
|
creationDate |
The date and time the ticket was created. |
modifiedDate |
The date and time the ticket was last modified. |
downloadURL |
If the data subject right includes the ability to download data, a unique URL to fetch from. |
id |
A unique identifier that can be used to track the status of the ticket. |
companyId |
Identifies the company associated with this ticket. |
A successful response looks like:
< HTTP/1.1 201 CREATED
< Content-Type: application/vnd.api+json
< Content-Length: 262
{
"data": [
{
"attributes": {
"apiKey": null
"creationDate": 1522451049000,
"deviceId": "ABCDEF01-0123-ABCD-ABCD-ABCDEF012345",
"deviceIdType": "IDFV",
"downloadUrl": null,
"modifiedDate": 1522451049000,
"status": "Processing",
"ticketType": "Objection",
"companyId": "686734"
},
"id": "3",
"type": "ticket"
}
]
}
Monitoring Request Status¶
After a ticket has been created, it can be monitored by periodically fetching the contents of the ticket and checking the status field. Tickets can be queried individually by specifying the type (ticket) and ticket identifier in the URL path:
curl -H"Authorization: Bearer $TOKEN" "https://datarequest.flurry.com/gdpr/v1/ticket/2"
A successful response looks like:
< HTTP/1.1 200 OK
< Content-Type: application/vnd.api+json
< Content-Length: 262
{
"data": {
"attributes": {
"apiKey": "ABCDEFGHIJKLMNOP",
"creationDate": 1522451049000,
"deviceId": "ABCDEF01-0123-ABCD-ABCD-ABCDEF012345",
"deviceIdType": "IDFV",
"downloadUrl": null,
"modifiedDate": 1522451049000,
"status": "Processing",
"ticketType": "Objection",
"companyId": "686734"
},
"id": "2",
"type": "ticket"
}
}
Downloading Data¶
Data will be accessible through a unique, signed URL per ticket. All download URLs expire after seven days. The downloaded data is encoded as a gzip compressed archive (tarball) consisting of two files:
data - a JSON encoded text file containing all of the data requested.
schema - a JSON schema formatted file which describes the structure of the data file.
It is expected that the schema format will evolve over time. Developers should only use the schema bundled together with the data when attempting decoding.
Example Request¶
curl "https://flurry-privacy.s3.amazonaws.com/downloads/792.json.gz?AWSAccessKeyId=ABCDEFG&Expires=1522693234&Signature=XYZABC123"
The latest schema can be downloaded here.
Expired URLs¶
The following error means that the presigned URL has expired:
<Error>
<Code>AccessDenied</Code>
<Message>Request has expired</Message>
<Expires>2017-12-12T17:53:55Z</Expires>
<ServerTime>2018-01-31T19:47:36Z</ServerTime>
<RequestId>ABCDEFG</RequestId>
<HostId>ABC/XYZ/AZ/1234=</HostId>
</Error>
Once expired, the underlying data is deleted and a new ticket must be created to fetch the same data.
Canceling Requests¶
Developers can cancel requests that have not yet started processing. For requests involving data erasure, Flurry will wait for up to 48 hours to begin processing the request. During this window, it will be possible to cancel requests by updating the status field of the original request ticket to the CANCELED state.
PLEASE NOTE: Once an Erasure request has been processed, it cannot be reversed. Cancellation of an Erasure MUST occur within the 48-hour window.
If Flurry cannot cancel the request because processing has begun, the API will return a 403
error to the caller.
HTTP PATCH
Requests are made against a specific ticket by specifying the type (ticket) and ticket identifier in the URL path:
curl -g -X PATCH -H"Content-Type: application/vnd.api+json" -H"Authorization: Bearer $TOKEN" "https://datarequest.flurry.com/gdpr/v1/ticket/2"
The entity body should specify the Canceled status:
{
"data": {
"type": "ticket",
"id": "2",
"attributes": {
"status": "Canceled"
}
}
}
On successful completion, the server returns:
HTTP/1.1 204 No Content
Searching For Prior Requests¶
The collection of pre-existing tickets can be searched, sorted, and paginated to aid in retrieving one or more prior requests. Searching and sorting is done against attributes in the ticket including:
creationDate
modifiedDate
status
apiKey
ticketType
deviceIdType
deviceId
All search requests are made against the collection of tickets with a base URL of:
https://datarequest.flurry.com/gdpr/v1/ticket
Filtering¶
The collection of pre-existing tickets is filtered by attaching a *filter[ticket] *query parameter to the request URL. The value of the filter parameter consists of one or more boolean predicates separated by conjunction (logical and) and disjunction (logical or) operators.
Predicates have a simple form: attributeName operator *value *with no intervening whitespace.
Supported Predicate Operator |
Description |
---|---|
|
Evaluates to true if the attribute exactly matches the provided value. |
|
Evaluates to true if the attribute exactly matches any of the provided values. The set of values is enclosed in parentheses and separated by commas. |
|
Evaluates to true if the attribute does not match any of the values. The set of values is enclosed in parentheses and separated by commas. |
|
Similar to SQL like |
|
Similar to SQL like |
|
Similar to SQL like |
|
Evaluates to true if the attribute is less than the value. |
|
Evaluates to true if the attribute is greater than the value. |
|
Evaluates to true if the attribute is less than or equal to the value. |
|
Evaluates to true if the attribute is greater than or equal to the value. |
|
Evaluates to true if the attribute is not set. |
|
Evaluates to true if the attribute is set. |
Multiple attribute predicates are separated by AND and OR logical operators (represented as ‘;’ and ‘,’ respectively):
Supported Logical Operator |
Description |
---|---|
|
Logical AND |
|
Logical OR |
The following examples illustrate some common filters:
Example |
Explanation |
---|---|
|
Where deviceId is equal to ‘abc’ OR ‘bcd’ |
|
Where the modification date is greater than Friday, March 30, 2018 11:01:36 PM |
|
Where apiKey is null. |
|
Where the ticket type equals Objection AND the apiey equals ABCDEFGHIJKLMNOP |
Sorting¶
Sorting by any attribute can be specified by the sort HTTP query parameter. The value is the attribute name optionally prepended with a ‘-’ for descending sort order. By default, sorting is ascending. For example:
curl -H"Authorization: Bearer $TOKEN" "https://datarequest.flurry.com/gdpr/v1/ticket?sort=-modifiedDate"
will return:
{
"data": [
{
"attributes": {
"apiKey": "ABCDEFGHMIJLMNOP",
"creationDate": 1522451049000,
"deviceId": "ABCDEF01-0123-ABCD-ABCD-ABCDEF012345",
"deviceIdType": "IDFV",
"downloadUrl": null,
"modifiedDate": 1522451049000,
"status": "Processing",
"ticketType": "Objection",
"companyId": "686734"
},
"id": "2",
"type": "ticket"
},
{
"attributes": {
"apiKey": null,
"creationDate": 1522450896000,
"deviceId": "ABCDEF01-0123-ABCD-ABCD-ABCDEF012345",
"deviceIdType": "IDFA",
"downloadUrl": "https://downloads.flurry.com/XYZ",
"modifiedDate": 1522450896000,
"status": "Acknowledged",
"ticketType": "Access",
"companyId": "686734"
},
"id": "1",
"type": "ticket"
}
]
}
Pagination¶
The collection of tickets can be paginated by optionally providing any of the three HTTP query parameters:
Query Parameter |
Description |
Value |
---|---|---|
|
The total number of tickets to return in the response. The size of the page. |
Any positive integer. |
|
Which page number to return (starting at 1). Each page has page[size] records. |
Any positive integer. |
|
Includes metadata in the response that states the total number of pages and total number of records. |
N/A |
For example, this request:
curl -H"Authorization: Bearer $TOKEN" "https://datarequest.flurry.com/gdpr/v1/ticket?page[size]=1&page[number]=1&page[totals]"
would return:
{
"data": [
{
"attributes": {
"apiKey": null,
"creationDate": 1522450896000,
"deviceId": "ABCDEF01-0123-ABCD-ABCD-ABCDEF012345",
"deviceIdType": "IDFA",
"downloadUrl": "https://downloads.flurry.com/XYZ",
"modifiedDate": 1522450896000,
"status": "Acknowledged",
"ticketType": "Access",
"companyId": "686734"
},
"id": "1",
"type": "ticket"
}
],
"meta": {
"page": {
"limit": 1,
"number": 1,
"totalPages": 2,
"totalRecords": 2
}
}
}
Whenever a pagination parameter is specified, the response includes a meta block that includes information about the current page and/or the total number of records & pages.
Errors¶
The following HTTP error codes are returned by the API:
HTTP |
Error Code Interpretation |
---|---|
200 |
Returned after a successful HTTP GET to query a ticket. |
201 |
Returned after a successful HTTP POST to create a ticket. |
204 |
Returned after a successful HTTP PATCH to cancel a ticket. |
400 |
The entity body or a query parameter was malformed. |
401 |
The client has invalid, expired, or missing credentials. |
403 |
The client does not have permission to perform the requested operation. |
408 |
The request took too long to complete. For search requests, consider using pagination or filters to restrict the response. |
429 |
The number of requests exceeded the limit allowed for a particular time window. The following headers will be returned with a 429 response:
|
500 |
The GDPR service had an internal error. The request can be reattempted at a later time/date. |
Testing¶
It is possible to test against the API by creating a ticket with a *Test *deviceIdType. Any string value can be used for the deviceId.
The service will immediately update test tickets to the *COMPLETE *status.
It should also be possible to download test data and the corresponding schema. However, the test data will be generic and not tied to the event model for your application(s).
Errors while downloading the File¶
Expired Request¶
If you see a response with the following body, the request needs to be created again using the request api. We normally store contents for upto 7 days:
"data":{
"attributes": {
"apiKey": null,
"creationDate": 1522450896000,
"deviceId": "ABCDEF01-0123-ABCD-ABCD-ABCDEF012345",
"deviceIdType": "IDFA",
"downloadUrl": "Expired URI. Please resubmit request",
"modifiedDate": 1522450896000,
"status": "Complete",
"ticketType": "Access",
"companyId": "686734"
},
"id": "1",
"type": "ticket"
}