Overview
The Data Export API enables you to automate the submission of data export requests and retrieve the results asynchronously. This approach ensures that large datasets can be processed efficiently without causing delays in the client application.
Authorization
To access the Data Export API, you need an API key. Please contact your Customer Success representative to obtain your unique API key for making data export requests.
Once you have your API key, include it as a Bearer token in the Authorization header of each API request, like this:
'Authorization': 'Bearer {your_api_key}'
API Endpoints
SubmitAsync
Endpoint: POST /export/external/v1/async/submit
This endpoint facilitates the submission of data export requests, providing a reference for the export in the response. This reference can then be utilized with the fetch API to retrieve the exported data at a later time.
The export request allows users to specify the data to be exported using various filtering options. Below are the details of the request model.
Request Message:
Export Request
Field | Type | IsRequired | Comment |
listRecords | ListRecordsRequest | Yes | Specifies what records to fetch. |
pageSize | Int | Yes | The maximum number of records is to be contained in a single file in the response. |
ListRecordsRequest
Field | Type | IsRequired | Comment |
filter | FilterQuery | No | Filters to narrow down the data. This is similar to filtering in the data using Enterpret query builder. |
timerange | TimeRange | Yes | The time range for which the data is to be fetched. |
FilterQuery
Please note that only of the filter, or operation field can be provided as part of this.
Field | Type | IsRequired | Comment |
filter | Filter | No | An individual filter, like filtering on metadata, labels, etc. |
operation | FilterQueryOperation | No | Multiple filters tied with operations like, OR, AND, etc. |
FilterQueryOperation
Field | Type | IsRequired | Comment |
op | String | Yes | Possible values are |
params | []FilterQuery | Yes | List of filter queries. This allows building in nested operations by creating a recursive relation between FilterQuery and FilterQueryOperation. |
Filter
Please note that only one of the following fields can be populated as part of a single filter clause.
Field | Type | IsRequired | Comment |
metadata | MetadataFilter | No | Filter based on metadata. |
languageISO6393 | String | No | Filter feedback from a particular language. |
source | string |
| Filter feedback from a particular source. |
hasLabel | HasLabelFilter | No | Filter feedback that has a Keyword, Reason, or Sentiment present on it. |
MetadataFilter
Field | Type | IsRequired | Comment |
key | String | Yes | Metadata Key to filter on. |
value | Value | Yes | Value for the key. |
op | String | No | Possible values are |
HasLabelFilter
Please note that only one of the following fields can be populated.
Field | Type | IsRequired | Comment |
aspect | Boolean | No | Filter records with keywords. |
reason | Boolean | No | Filter records with reasons. |
sentiment | Boolean | No | Filter records with sentiment. |
TimeRange
Either provide the startTime
and the endTime
or provide the relative
time.
Field | Type | IsRequired | Comment |
startTime | int64 | No | Start time in epoch seconds. |
endTime | int64 | No | Endtime in epoch seconds. |
relative | RelativeTimeRange | No | Relative time. |
RelativeTimeRange
Field | Type | IsRequired | Comment |
value | int64 | Yes |
|
unit | String | Yes | Possible values are, |
Example Requests
Example Requests
To submit a data export request, send a POST
request to /export/external/v1/async/submit
with the following JSON payload:
{
"pageSize": 100,
"listRecords": {
// Your listRecords request payload
}
}
Request to get last 30 days of data from ZendeskSupport or Twitter.
{
"listRecords": {
"filter": {
"operation": {
"op": "OR",
"params": [
{
"filter": {
"source": "ZendeskSupport"
}
},
{
"filter": {
"source": "Twitter"
}
}
]
}
},
"timerange": {
"relative": {
"unit": "Day",
"value": "30"
}
}
},
"pageSize": "5000"
}
Request to get the last 2 months' data from ZendeskSupport or Twitter, only if it has a Reason prediction on it.
{
"listRecords": {
"filter": {
"operation": {
"op": "AND",
"params": [
{
"filter": {
"hasLabel": {
"reason": true
}
}
},
{
"operation": {
"op": "OR",
"params": [
{
"filter": {
"source": "ZendeskSupport"
}
},
{
"filter": {
"source": "Twitter"
}
}
]
}
}
]
}
},
"timerange": {
"relative": {
"unit": "Month",
"value": "2"
}
}
},
"pageSize": "5000"
}
Response Message:
Field | Type | IsRequired | Comment |
queryID | String | Yes | Reference for the submitted query |
Example Response
Example Response
{
"queryID": "async-avi5jg"
}
FetchAsync
Fetches the results of an asynchronous data export request created with the previous API.
Endpoint: POST /export/external/v1/async/fetch
Request Message:
Field | Type | IsRequired | Comment |
queryID | String | Yes | QueryID that we received as the response from the previous API. |
Example Request
Example Request
To fetch the results of a previously submitted data export request, send a POST
request to /external/v1/async/fetch
with the following JSON payload:
{
"queryID": "your-query-id"
}
e.g
{
"queryID": "async-avi5jg"
}
Response Message
If the export is in progress, the API returns an empty JSON, with a 200 Status Code. Once the export request is completed, it contains the following response.
Field | Type | IsRequired | Comment |
responseFiles | []String | Yes | List of files containing the feedback data. Do note that the link to these files expires in a few hours, so process the content before that. |
The file content is JSON, which has the following schema.
File Content Schema
Field | Type | IsRequired | Comment |
records | []FeedbackRecord | Yes | List of feedback records. |
FeedbackRecord
Field | Type | IsRequired | Comment |
id | String | Yes | ID of the Record |
source | String | Yes | Source of the Record |
orgID | String | Yes | OrgID that record belongs to. |
type | String | Yes |
|
sourceTimestamp | int | Yes | sourceTimestamp is timestamp of the feedback as reported by the source
|
ingestedAt | int | Yes | timestamp when feedback was ingested to Enterpret. |
languages | []Language | Yes | languages detected in the original feedback. |
originRecordID | String | Yes | Unique identifier for the records in the source system. |
predictions | []Prediction | No | Ids of the predictions for this Feedback Records. We can resolve the label names and other details for these using the label API discussed later. |
summary | []String | No | Summaries for the feedback record. |
text | TextContent | No | Present if feedback is of type review. |
conversation | ConversationContent | No | Present if feedback is of type conversation, or forum thread. |
survey | SurveyContent | No | Present if the feedback is of type survey. |
TextContent
Field | Type | IsRequired | Comment |
text | String | Yes | Text of feedback. |
textEn | String | No | Text of feedback in English, if the original feedback is in non-english. |
ConversationContent
Field | Type | IsRequired | Comment |
conversation | Conversation | Yes | content of the feedback. |
conversationEn | Conversation | No | content of feedback in English, if the original feedback is in non-English. |
Conversation
Field | Type | IsRequired | Comment |
msgs | []ConversationMessage | Yes | msgs in the conversation |
ConversationMessage
Field | Type | IsRequired | Comment |
text | String | Yes | content of the feedback. |
actor | String | Yes |
|
actorID | String | No | Id of the actor. |
SurveyContent
Field | Type | IsRequired | Comment |
surveyResponse | SurveyResponse | Yes | content of the feedback. |
surveyResponseEn | SurveyResponse | Yes | content of the feedback in English, if the original feedback is in non-English. |
SurveyResponse
Field | Type | IsRequired | Comment |
responses | []SurveyQA | Yes | list of question answer in the survey |
SurveyQA
Field | Type | IsRequired | Comment |
question | String | Yes |
|
answer | String | Yes |
|
selectedOptions | []String | No | for multi-select questions. |
Example File Content
Example File Content
{
"records": [
{
"id": "f742cf1a-382c-5af1-9f3c-4989c40c95d1",
"source": "Playstore",
"orgID": "573c9d74-bdc4-4c6d-bd40-3465658ab47b",
"type": "RecordTypeReview",
"metadata": {
"keyValues": {
"App id": {
"s": [
"us.zoom.videomeetings"
]
},
"Id": {
"s": [
"b2578def-37c4-4afd-a763-bbc81a11a1cc"
]
},
"Language code": {
"s": [
"bg"
]
},
"Language name": {
"s": [
"Bulgarian"
]
},
"Rating": {
"n": [
1
]
},
"Reviewer": {
"s": [
"owilli samuel"
]
},
"Timestamp": {
"n": [
1721302100
]
},
"Useful": {
"n": [
0
]
},
"Version": {
"s": [
"6.0.12.22225"
]
}
}
},
"sourceTimestamp": "1721302089",
"ingestedAt": "1721389257",
"languages": [
{
"iso6393": "eng",
"iso6391": "en",
"source": "detected"
}
],
"predictions": [
{
"labelID": "b818b657-3d0f-4113-bb54-f4bf8a2bbdfa",
"sentiment": {}
},
{
"labelID": "f7794882-b8cc-42fa-afd3-8159cf618b21",
"reason": {
"units": [
{
"confidence": 1.0000001
}
]
}
}
],
"summary": [
"The user has an extremely positive experience with Zoom, but does not provide specific details.",
"happy with zoom::praise"
],
"text": {
"text": "Extremely good."
}
},
{
"id": "9c014e6d-475e-5589-bca6-3f5dd96f45e7",
"source": "Playstore",
"orgID": "573c9d74-bdc4-4c6d-bd40-3465658ab47b",
"type": "RecordTypeReview",
"metadata": {
"keyValues": {
"App id": {
"s": [
"us.zoom.videomeetings"
]
},
"Id": {
"s": [
"9d23cbf4-e8fd-4d36-8d78-45dd4915da75"
]
},
"Language code": {
"s": [
"uk"
]
},
"Language name": {
"s": [
"Ukrainian"
]
},
"Rating": {
"n": [
5
]
},
"Reviewer": {
"s": [
"Haribhan Bhatt"
]
},
"Timestamp": {
"n": [
1721300500
]
},
"Useful": {
"n": [
0
]
},
"Version": {
"s": [
"6.1.1.22710"
]
}
}
},
"sourceTimestamp": "1721300532",
"ingestedAt": "1721389333",
"languages": [
{
"iso6393": "eng",
"iso6391": "en",
"source": "detected"
}
],
"text": {
"text": "Nice"
}
},
{
"id": "acfc9e64-60f4-5098-9617-ffb1259b9d1b",
"source": "Playstore",
"orgID": "573c9d74-bdc4-4c6d-bd40-3465658ab47b",
"type": "RecordTypeReview",
"metadata": {
"keyValues": {
"App id": {
"s": [
"us.zoom.videomeetings"
]
},
"Id": {
"s": [
"577b0588-ff98-439b-8210-2bdcb6ad7bd9"
]
},
"Language code": {
"s": [
"vi"
]
},
"Language name": {
"s": [
"Vietnamese"
]
},
"Rating": {
"n": [
2
]
},
"Reviewer": {
"s": [
"Vivek Kamble"
]
},
"Timestamp": {
"n": [
1721300500
]
},
"Useful": {
"n": [
0
]
},
"Version": {
"s": [
"6.1.1.22710"
]
}
}
},
"sourceTimestamp": "1721300422",
"ingestedAt": "1721389333",
"languages": [
{
"iso6393": "eng",
"iso6391": "en",
"source": "detected"
}
],
"predictions": [
{
"labelID": "e9c9fc3b-093b-47b9-8432-534d40558e88",
"reason": {
"units": [
{
"confidence": 0.9706277
}
]
}
},
{
"labelID": "b21c5a63-db73-4d13-b4d4-3c30b85029d2",
"sentiment": {}
}
],
"summary": [
"The user is dissatisfied with the Zoom app's performance on Android mobiles.",
"issue with zoom app performance on android mobiles::issue"
],
"text": {
"text": "Poor app to use on android mobiles"
}
}
]
}
Labels API
https://api.enterpret.com/export/external/v1/labels/<labelid>
Response:
{
"label": {
"id": "88aea2c2-3fb6-489a-99b9-72adddc78e6f",
"displayName": "Issue With Uploading Documents",
"type": "REASON",
"description": "Customers face issues with the document upload process, encountering problems with the file upload feature provided by Enterpret Inc.",
"createdAt": "1699632396",
"isVisible": true,
"metadata": {
"reason": {
"sentiment": "NEUTRAL",
"representativeSentences": [
"issue uploading documents",
"issue with file upload feature"
],
"type": "MANUALLY_DEFINED"
}
}
}
}
Sample Scripts
Bash
Bash
#!/bin/bash
# Constants
API_TOKEN="Bearer <api-token>" # Replace with a valid API token
SUBMIT_URL="https://api.enterpret.com/export/external/v1/async/submit"
FETCH_URL="https://api.enterpret.com/export/external/v1/async/fetch"
CHECK_DELAY=10 # Delay in seconds between status checks
# JSON payload for the query
payload=$(cat <<EOF
{
"listRecords": {
"pageSize": 5000,
"filter": {
"operation": {
"op": "OR",
"params": [
{
"filter": {
"source": "Qualtrics"
}
}
]
}
},
"timerange": {
"relative": {
"unit": "Day",
"value": 30
}
}
}
}
EOF
)
# Function to submit the query and retrieve the query ID
submit_query() {
response=$(curl -s -X POST "$SUBMIT_URL" \
-H "Authorization: $API_TOKEN" \
-H "Content-Type: application/json" \
-d "$payload")
query_id=$(echo "$response" | jq -r '.queryID')
if [[ "$query_id" == "null" ]]; then
echo "Failed to retrieve query ID. Response: $response"
exit 1
fi
echo "Query submitted. Query ID: $query_id"
echo "$query_id"
}
# Function to fetch the results of the query
fetch_results() {
local query_id="$1"
while true; do
response=$(curl -s -X POST "$FETCH_URL" \
-H "Authorization: $API_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"queryID\": \"$query_id\"}")
is_completed=$(echo "$response" | jq -r '.isCompleted')
if [[ "$is_completed" == "true" ]]; then
echo "Query completed. Response files:"
echo "$response" | jq -r '.responseFiles[]'
break
else
echo "Query still in progress. Checking again in $CHECK_DELAY seconds..."
sleep "$CHECK_DELAY"
fi
done
}
# Main script execution
query_id=$(submit_query)
fetch_results "$query_id"
Python
Python
import requests
import time
# Constants
API_TOKEN = 'Bearer <api-token>' # Replace with a valid API token
SUBMIT_URL = 'https://api.enterpret.com/export/external/v1/async/submit'
FETCH_URL = 'https://api.enterpret.com/export/external/v1/async/fetch'
HEADERS = {'Authorization': API_TOKEN}
# Function to submit the query and retrieve query ID
def submit_query():
payload = {
"listRecords": {
"pageSize": 5000,
"filter": {
"operation": {
"op": "OR",
"params": [
{
"filter": {
"source": "Qualtrics"
}
}
]
}
},
"timerange": {
"relative": {
"unit": "Day",
"value": 30
}
}
}
}
response = requests.post(SUBMIT_URL, headers=HEADERS, json=payload)
if response.status_code == 200:
data = response.json()
return data.get('queryID')
else:
response.raise_for_status()
# Function to fetch query results once completed
def fetch_results(query_id):
while True:
response = requests.post(FETCH_URL, headers=HEADERS, json={"queryID": query_id})
if response.status_code != 200:
response.raise_for_status()
result = response.json()
if result.get("isCompleted"):
print("Query completed. Response files:")
for file_url in result.get("responseFiles", []):
print(file_url)
break
else:
print("Query still in progress. Checking again in 10 seconds...")
time.sleep(10)
# Run the query and retrieve results
query_id = submit_query()
if query_id:
print(f"Query submitted. Query ID: {query_id}")
fetch_results(query_id)
else:
print("Failed to retrieve Query ID.")
Golang
Golang
package main
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"time"
)
// Constants
const (
apiToken = "Bearer <api-token>" // Replace with a valid API token
submitURL = "https://api.enterpret.com/export/external/v1/async/submit"
fetchURL = "https://api.enterpret.com/export/external/v1/async/fetch"
checkDelay = 10 * time.Second // Delay between checks for completion
)
// Payload for the query request
var payload = map[string]interface{}{
"listRecords": map[string]interface{}{
"pageSize": 5000,
"filter": map[string]interface{}{
"operation": map[string]interface{}{
"op": "OR",
"params": []map[string]interface{}{
{
"filter": map[string]interface{}{
"source": "Qualtrics",
},
},
},
},
},
"timerange": map[string]interface{}{
"relative": map[string]interface{}{
"unit": "Day",
"value": 30,
},
},
},
}
// Function to submit the query and retrieve the query ID
func submitQuery() (string, error) {
payloadBytes, _ := json.Marshal(payload)
req, err := http.NewRequest("POST", submitURL, bytes.NewBuffer(payloadBytes))
if err != nil {
return "", err
}
req.Header.Set("Authorization", apiToken)
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("request failed with status %d", resp.StatusCode)
}
var result map[string]interface{}
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return "", err
}
queryID, ok := result["queryID"].(string)
if !ok {
return "", fmt.Errorf("queryID not found in response")
}
return queryID, nil
}
// Function to fetch the results of the query
func fetchResults(queryID string) error {
for {
// Set up the request body with the query ID
body, _ := json.Marshal(map[string]string{"queryID": queryID})
req, err := http.NewRequest("POST", fetchURL, bytes.NewBuffer(body))
if err != nil {
return err
}
req.Header.Set("Authorization", apiToken)
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("fetch request failed with status %d", resp.StatusCode)
}
var result map[string]interface{}
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return err
}
if isCompleted, ok := result["isCompleted"].(bool); ok && isCompleted {
fmt.Println("Query completed. Response files:")
if responseFiles, ok := result["responseFiles"].([]interface{}); ok {
for _, file := range responseFiles {
fmt.Println(file)
}
}
break
} else {
fmt.Println("Query still in progress. Checking again in 10 seconds...")
time.Sleep(checkDelay)
}
}
return nil
}
func main() {
queryID, err := submitQuery()
if err != nil {
log.Fatalf("Failed to submit query: %v", err)
}
fmt.Printf("Query submitted. Query ID: %s\n", queryID)
err = fetchResults(queryID)
if err != nil {
log.Fatalf("Failed to fetch results: %v", err)
}
}
JavaScript
JavaScript
const axios = require('axios');
// Constants
const API_TOKEN = 'Bearer <api-token>'; // Replace with a valid API token
const SUBMIT_URL = 'https://api.enterpret.com/export/external/v1/async/submit';
const FETCH_URL = 'https://api.enterpret.com/export/external/v1/async/fetch';
const CHECK_DELAY = 10000; // 10 seconds
// Payload for the query request
const payload = {
listRecords: {
pageSize: 5000,
filter: {
operation: {
op: "OR",
params: [
{
filter: {
source: "Qualtrics"
}
}
]
}
},
timerange: {
relative: {
unit: "Day",
value: 30
}
}
}
};
// Function to submit the query and retrieve the query ID
async function submitQuery() {
try {
const response = await axios.post(SUBMIT_URL, payload, {
headers: {
Authorization: API_TOKEN,
'Content-Type': 'application/json'
}
});
if (response.status === 200) {
return response.data.queryID;
} else {
throw new Error(`Request failed with status ${response.status}`);
}
} catch (error) {
console.error("Error submitting query:", error.message);
throw error;
}
}
// Function to fetch the results of the query
async function fetchResults(queryID) {
while (true) {
try {
const response = await axios.post(FETCH_URL, { queryID }, {
headers: {
Authorization: API_TOKEN,
'Content-Type': 'application/json'
}
});
if (response.status === 200) {
const result = response.data;
if (result.isCompleted) {
console.log("Query completed. Response files:");
result.responseFiles.forEach(fileUrl => {
console.log(fileUrl);
});
break;
} else {
console.log("Query still in progress. Checking again in 10 seconds...");
await new Promise(resolve => setTimeout(resolve, CHECK_DELAY));
}
} else {
throw new Error(`Fetch request failed with status ${response.status}`);
}
} catch (error) {
console.error("Error fetching results:", error.message);
throw error;
}
}
}
// Main function to run the query and fetch results
(async () => {
try {
const queryID = await submitQuery();
console.log(`Query submitted. Query ID: ${queryID}`);
await fetchResults(queryID);
} catch (error) {
console.error("Process failed:", error.message);
}
})();
Troubleshooting
If you encounter any issues while using the Data Export API, please refer to the following common problems and solutions:
Invalid API Key: Ensure that you have included the correct API key as Bearer token in the Authorization header.
Invalid Request: Make sure all required fields are included in your request payload.
Server Errors: If you receive a server error, contact Enterpret support for assistance.
For further assistance, please reach out to Enterpret support or your customer success representative.