Skip to content
Sali Apps
All Feedloop docs

Data export API

Pull your raw response data programmatically as JSON or CSV. Pro plan.


The data export API lets you pull your raw response data as JSON or CSV from your own scripts, a scheduled job, or a BI tool. It is read-only, server-to-server, and returns the same data as the CSV export in the admin.

The data export API requires the Pro plan. On Free and Growth you can still export everything to CSV by hand from the Responses page.

Authentication

Every request is authenticated with your store's own API key, sent as a bearer token.

Generate your key

In the Feedloop admin, open Settings and find the API access section. A few things to know:

  • The key is shown once, at the moment you generate it. Copy it straight away, because Feedloop stores only a hash of it and cannot show it to you again.
  • The key starts with fdlp_ followed by 48 hexadecimal characters.
  • Generating a new key replaces the old one. The previous key stops working immediately, which is also how you revoke a key you think has leaked.
  • If your store drops below the Pro plan the key stops working, and starts working again if you return to Pro. You do not need to regenerate it.

Send it on every request

Put the key in the Authorization header as a bearer token:

Authorization: Bearer fdlp_your_key_here

Never put the key in a URL or in client-side code. It grants read access to all of your response data.

Endpoint

GET https://feedloop.saliapps.com/api/v1/export/responses

Quick start

curl -s https://feedloop.saliapps.com/api/v1/export/responses \
  -H "Authorization: Bearer fdlp_your_key_here"

That returns the first page of responses as JSON. The examples further down add date filtering, CSV output, and pagination.

Query parameters

Parameter Type Default Description
since ISO 8601 date or datetime none Only responses submitted at or after this time, for example 2026-07-01 or 2026-07-01T00:00:00Z.
format json or csv json The response encoding.
limit integer, 1 to 250 250 Page size. Values above 250 are capped at 250.
cursor response id none Return the page that follows this response id. Pass the nextCursor from the previous page.

Responses are always ordered by ascending id, which is what keeps the cursor stable as new responses arrive.

JSON response

{
  "responses": [
    {
      "id": "cms7a5af6000nvln4ogvkij0w",
      "submittedAt": "2026-07-28T13:57:01.705Z",
      "survey": "Post-purchase NPS",
      "source": "THANK_YOU",
      "npsScore": 1,
      "orderId": "5079105710",
      "recoveryCode": "FEEDLOOP-A1B2C3",
      "answers": [
        { "question": "How likely are you to recommend us?", "type": "NPS", "value": "1" },
        { "question": "What let us down?", "type": "TEXT", "value": "Shipping was slow" }
      ]
    }
  ],
  "nextCursor": "cms7a5af6000nvln4ogvkij0w"
}

Fields

Field Type Notes
id string The response id. This is also the pagination cursor.
submittedAt string ISO 8601 timestamp of the submission.
survey string The survey's name.
source string Where it was answered: THANK_YOU, ORDER_STATUS, or THEME_EMBED (the on-site pop-up).
npsScore integer or null 0 to 10, or null if the survey had no NPS question.
orderId string or null The Shopify order id, or null for anonymous pop-up responses.
recoveryCode string or null The detractor discount code, if one was created for this response.
answers array One entry per answered question.
answers[].question string The question prompt.
answers[].type string NPS, SINGLE_CHOICE, MULTI_CHOICE, or TEXT.
answers[].value string The answer. Multiple-choice selections are joined with a comma and a space.
nextCursor string or null Top level. The id to pass as cursor for the next page, or null when there are no more pages.

CSV response

Add format=csv to get a flat file instead of JSON:

curl -s "https://feedloop.saliapps.com/api/v1/export/responses?since=2026-07-01&format=csv" \
  -H "Authorization: Bearer fdlp_your_key_here" \
  -o responses.csv

The columns are:

response_id,submitted_at,survey,source,nps_score,order_id,question,answer,recovery_code

There is one row per answer, so a response with three answers spans three rows that share the same response_id. A response with no answers still emits one row, with the question and answer columns left blank, so nothing silently disappears from the file.

Pagination

Each page returns up to limit responses (250 by default) and a nextCursor. Pass that cursor back to get the next page. When nextCursor is null, you have reached the end.

Here is a full loop in bash that walks every page, using jq to read the cursor:

KEY="fdlp_your_key_here"
BASE="https://feedloop.saliapps.com/api/v1/export/responses"
cursor=""
while true; do
  url="$BASE?limit=250"
  if [ -n "$cursor" ]; then url="$url&cursor=$cursor"; fi
  page=$(curl -s "$url" -H "Authorization: Bearer $KEY")
  echo "$page" | jq -c '.responses[]'
  cursor=$(echo "$page" | jq -r '.nextCursor // empty')
  if [ -z "$cursor" ]; then break; fi
done

Rate limits

The endpoint allows up to 60 requests per 60 seconds per store. Beyond that it returns 429 rate_limited; wait a moment and retry. At a page size of 250 that is up to 15,000 responses a minute, far more than any single export needs.

Errors

Errors return the matching HTTP status and a JSON body shaped like { "error": { "code", "message" } }.

Status code When
400 validation_error since is not a valid ISO 8601 date.
401 auth_error The Authorization header is missing or malformed, the key is unknown or was revoked, or the store is not on Pro.
429 rate_limited More than 60 requests in 60 seconds.
500 internal_error An unexpected server error.
{ "error": { "code": "auth_error", "message": "The data export API requires the Pro plan" } }