curl --request GET \
--url https://app.formbricks.com/api/v3/responses \
--cookie next-auth.session-token=import requests
url = "https://app.formbricks.com/api/v3/responses"
headers = {"cookie": "next-auth.session-token="}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {cookie: 'next-auth.session-token='}};
fetch('https://app.formbricks.com/api/v3/responses', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://app.formbricks.com/api/v3/responses",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_COOKIE => "next-auth.session-token=",
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://app.formbricks.com/api/v3/responses"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("cookie", "next-auth.session-token=")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://app.formbricks.com/api/v3/responses")
.header("cookie", "next-auth.session-token=")
.asString();require 'uri'
require 'net/http'
url = URI("https://app.formbricks.com/api/v3/responses")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["cookie"] = 'next-auth.session-token='
response = http.request(request)
puts response.read_body{
"data": [
{
"id": "clrs1234567890123456789012",
"surveyId": "clsv1234567890123456789012",
"surveyName": "Q3 Product Feedback",
"workspaceId": "clws1234567890123456789012",
"createdAt": "2026-08-27T09:14:03.417Z",
"updatedAt": "2026-08-27T09:17:41.902Z",
"finished": true,
"endingId": "cmpthankyou00000000000000",
"language": "de",
"durationSeconds": 107.9,
"resolution": {
"labelPolicy": "currentSurveyDefinition",
"labelsLanguage": "de",
"surveyUpdatedAt": "2026-08-20T11:02:55.000Z"
},
"answers": [
{
"position": 1,
"elementId": "nps_score",
"elementType": "nps",
"elementLabel": "Wie wahrscheinlich ist es, dass Sie uns weiterempfehlen?",
"valueNumber": 9,
"range": {
"min": 0,
"max": 10
},
"durationSeconds": 12.1
},
{
"position": 2,
"elementId": "what_could_be_better",
"elementType": "openText",
"elementLabel": "Was koennten wir besser machen?",
"valueText": "Die Suche ist zu langsam.",
"inputType": "text",
"durationSeconds": 64.8
},
{
"position": 3,
"elementId": "channels",
"elementType": "multipleChoiceMulti",
"elementLabel": "Wie haben Sie von uns erfahren?",
"selections": [
{
"optionId": "ch_2f8a",
"optionLabel": "Ein Freund",
"rawValue": "Ein Freund",
"match": "label"
},
{
"optionId": "other",
"optionLabel": "Andere",
"rawValue": "Podcast",
"match": "other"
},
{
"optionId": null,
"optionLabel": null,
"rawValue": "Twitter",
"match": "unmatched"
}
],
"durationSeconds": 31
},
{
"position": 4,
"elementId": "consent_terms",
"elementType": "consent",
"elementLabel": "Ich stimme den Bedingungen zu",
"valueBoolean": true,
"rawValue": "accepted"
}
],
"embeddedData": [
{
"key": "plan",
"kind": "ingested",
"type": "string",
"label": "Plan",
"value": "enterprise"
},
{
"key": "score",
"kind": "computed",
"type": "number",
"label": "Score",
"value": 12
},
{
"key": "country",
"kind": "reserved",
"type": "string",
"label": "Country",
"value": "DE"
}
],
"unresolved": [
{
"key": "old_question_id",
"rawValue": "some answer",
"reason": "elementNotInSurvey"
}
],
"tags": [
{
"id": "cltg1234567890123456789012",
"name": "vip"
}
]
}
],
"meta": {
"limit": 20,
"totalCountRelation": null,
"nextCursor": "eyJ2ZXJzaW9uIjoxLCJraW5kIjoicmVzcG9uc2VzIiwic29ydEJ5IjoiLWNyZWF0ZWRBdCIsImZwIjoiZTNpT0pQNkN4V0p0TEZTOSIsInZhbHVlIjoiMjAyNi0wOC0yN1QwOToxNDowMy40MTdaIiwiaWQiOiJjbHJzMTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMiJ9",
"totalCount": null
}
}{
"title": "<string>",
"status": 123,
"detail": "<string>",
"requestId": "<string>",
"type": "<string>",
"instance": "<string>",
"code": "ai_features_not_enabled",
"details": {},
"invalid_params": [
{
"name": "<string>",
"reason": "<string>",
"code": "dangling_reference",
"identifier": "<string>",
"referenceType": "block",
"missingId": "<string>",
"firstUsedAt": "<string>",
"conflictsWith": "<string>"
}
]
}{
"title": "<string>",
"status": 123,
"detail": "<string>",
"requestId": "<string>",
"type": "<string>",
"instance": "<string>",
"code": "ai_features_not_enabled",
"details": {},
"invalid_params": [
{
"name": "<string>",
"reason": "<string>",
"code": "dangling_reference",
"identifier": "<string>",
"referenceType": "block",
"missingId": "<string>",
"firstUsedAt": "<string>",
"conflictsWith": "<string>"
}
]
}{
"title": "<string>",
"status": 123,
"detail": "<string>",
"requestId": "<string>",
"type": "<string>",
"instance": "<string>",
"code": "ai_features_not_enabled",
"details": {},
"invalid_params": [
{
"name": "<string>",
"reason": "<string>",
"code": "dangling_reference",
"identifier": "<string>",
"referenceType": "block",
"missingId": "<string>",
"firstUsedAt": "<string>",
"conflictsWith": "<string>"
}
]
}{
"title": "<string>",
"status": 123,
"detail": "<string>",
"requestId": "<string>",
"type": "<string>",
"instance": "<string>",
"code": "ai_features_not_enabled",
"details": {},
"invalid_params": [
{
"name": "<string>",
"reason": "<string>",
"code": "dangling_reference",
"identifier": "<string>",
"referenceType": "block",
"missingId": "<string>",
"firstUsedAt": "<string>",
"conflictsWith": "<string>"
}
]
}{
"title": "<string>",
"status": 123,
"detail": "<string>",
"requestId": "<string>",
"type": "<string>",
"instance": "<string>",
"code": "ai_features_not_enabled",
"details": {},
"invalid_params": [
{
"name": "<string>",
"reason": "<string>",
"code": "dangling_reference",
"identifier": "<string>",
"referenceType": "block",
"missingId": "<string>",
"firstUsedAt": "<string>",
"conflictsWith": "<string>"
}
]
}{
"title": "<string>",
"status": 123,
"detail": "<string>",
"requestId": "<string>",
"type": "<string>",
"instance": "<string>",
"code": "ai_features_not_enabled",
"details": {},
"invalid_params": [
{
"name": "<string>",
"reason": "<string>",
"code": "dangling_reference",
"identifier": "<string>",
"referenceType": "block",
"missingId": "<string>",
"firstUsedAt": "<string>",
"conflictsWith": "<string>"
}
]
}List responses
Returns responses for a workspace, newest first. Session cookie, x-api-key, or a Bearer token, with read access.
Scoping. workspaceId is required, as on every v3 collection read. Add surveyId to narrow to one survey — the common case, and the one the storage layer is indexed for ((surveyId, createdAt)). A workspace-wide list has to reach responses through their surveys and is correspondingly slower, so pass surveyId whenever you know it. contactId narrows to one respondent.
Pagination is keyset, on (createdAt, id). The cursor is opaque and bound to the sort order and the filter set it was issued for; changing either invalidates it. meta.nextCursor being null is the only end-of-collection signal — do not infer the end from a short page.
id is there purely as a tie-breaker, and it makes the order total, so no row is ever skipped or repeated across pages. What it is not is a finer-grained clock: createdAt is stored to millisecond precision, so responses sharing a millisecond — everything written in one bulk insert, for instance — come back in an order that is stable and repeatable but is not their true creation order. Do not read the sequence within a millisecond as meaningful.
Filters are allow-listed, and each one on its own is either served by an index or bounded by one. filter[finished][eq] and filter[language][in] additionally require surveyId or contactId, because both are heap residuals that are only bounded when the scan is anchored to one survey or one contact; sending either without such a scope returns 400.
Be aware that combining a filter[createdAt] bound with filter[finished][eq] is the one case no single index covers: the ordered scan uses (surveyId, createdAt) and finished stays a heap residual, so a page whose matches are sparse reads far more rows than it returns. Prefer narrowing by surveyId and a tight createdAt window, and treat that combination as the slow path until a covering index exists.
What this endpoint deliberately does not filter on: individual answers, hidden-field values, submission metadata (source, url, country, browser, os, deviceType), contact attributes, quotas, and tags. Each is a JSON path or an unindexed join on the largest table in the database, where the honest outcome is a timeout rather than a slow query. The in-app dashboard does offer these, so this endpoint is knowingly behind it; closing the gap needs index work and is tracked separately rather than quietly omitted.
curl --request GET \
--url https://app.formbricks.com/api/v3/responses \
--cookie next-auth.session-token=import requests
url = "https://app.formbricks.com/api/v3/responses"
headers = {"cookie": "next-auth.session-token="}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {cookie: 'next-auth.session-token='}};
fetch('https://app.formbricks.com/api/v3/responses', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://app.formbricks.com/api/v3/responses",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_COOKIE => "next-auth.session-token=",
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://app.formbricks.com/api/v3/responses"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("cookie", "next-auth.session-token=")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://app.formbricks.com/api/v3/responses")
.header("cookie", "next-auth.session-token=")
.asString();require 'uri'
require 'net/http'
url = URI("https://app.formbricks.com/api/v3/responses")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["cookie"] = 'next-auth.session-token='
response = http.request(request)
puts response.read_body{
"data": [
{
"id": "clrs1234567890123456789012",
"surveyId": "clsv1234567890123456789012",
"surveyName": "Q3 Product Feedback",
"workspaceId": "clws1234567890123456789012",
"createdAt": "2026-08-27T09:14:03.417Z",
"updatedAt": "2026-08-27T09:17:41.902Z",
"finished": true,
"endingId": "cmpthankyou00000000000000",
"language": "de",
"durationSeconds": 107.9,
"resolution": {
"labelPolicy": "currentSurveyDefinition",
"labelsLanguage": "de",
"surveyUpdatedAt": "2026-08-20T11:02:55.000Z"
},
"answers": [
{
"position": 1,
"elementId": "nps_score",
"elementType": "nps",
"elementLabel": "Wie wahrscheinlich ist es, dass Sie uns weiterempfehlen?",
"valueNumber": 9,
"range": {
"min": 0,
"max": 10
},
"durationSeconds": 12.1
},
{
"position": 2,
"elementId": "what_could_be_better",
"elementType": "openText",
"elementLabel": "Was koennten wir besser machen?",
"valueText": "Die Suche ist zu langsam.",
"inputType": "text",
"durationSeconds": 64.8
},
{
"position": 3,
"elementId": "channels",
"elementType": "multipleChoiceMulti",
"elementLabel": "Wie haben Sie von uns erfahren?",
"selections": [
{
"optionId": "ch_2f8a",
"optionLabel": "Ein Freund",
"rawValue": "Ein Freund",
"match": "label"
},
{
"optionId": "other",
"optionLabel": "Andere",
"rawValue": "Podcast",
"match": "other"
},
{
"optionId": null,
"optionLabel": null,
"rawValue": "Twitter",
"match": "unmatched"
}
],
"durationSeconds": 31
},
{
"position": 4,
"elementId": "consent_terms",
"elementType": "consent",
"elementLabel": "Ich stimme den Bedingungen zu",
"valueBoolean": true,
"rawValue": "accepted"
}
],
"embeddedData": [
{
"key": "plan",
"kind": "ingested",
"type": "string",
"label": "Plan",
"value": "enterprise"
},
{
"key": "score",
"kind": "computed",
"type": "number",
"label": "Score",
"value": 12
},
{
"key": "country",
"kind": "reserved",
"type": "string",
"label": "Country",
"value": "DE"
}
],
"unresolved": [
{
"key": "old_question_id",
"rawValue": "some answer",
"reason": "elementNotInSurvey"
}
],
"tags": [
{
"id": "cltg1234567890123456789012",
"name": "vip"
}
]
}
],
"meta": {
"limit": 20,
"totalCountRelation": null,
"nextCursor": "eyJ2ZXJzaW9uIjoxLCJraW5kIjoicmVzcG9uc2VzIiwic29ydEJ5IjoiLWNyZWF0ZWRBdCIsImZwIjoiZTNpT0pQNkN4V0p0TEZTOSIsInZhbHVlIjoiMjAyNi0wOC0yN1QwOToxNDowMy40MTdaIiwiaWQiOiJjbHJzMTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMiJ9",
"totalCount": null
}
}{
"title": "<string>",
"status": 123,
"detail": "<string>",
"requestId": "<string>",
"type": "<string>",
"instance": "<string>",
"code": "ai_features_not_enabled",
"details": {},
"invalid_params": [
{
"name": "<string>",
"reason": "<string>",
"code": "dangling_reference",
"identifier": "<string>",
"referenceType": "block",
"missingId": "<string>",
"firstUsedAt": "<string>",
"conflictsWith": "<string>"
}
]
}{
"title": "<string>",
"status": 123,
"detail": "<string>",
"requestId": "<string>",
"type": "<string>",
"instance": "<string>",
"code": "ai_features_not_enabled",
"details": {},
"invalid_params": [
{
"name": "<string>",
"reason": "<string>",
"code": "dangling_reference",
"identifier": "<string>",
"referenceType": "block",
"missingId": "<string>",
"firstUsedAt": "<string>",
"conflictsWith": "<string>"
}
]
}{
"title": "<string>",
"status": 123,
"detail": "<string>",
"requestId": "<string>",
"type": "<string>",
"instance": "<string>",
"code": "ai_features_not_enabled",
"details": {},
"invalid_params": [
{
"name": "<string>",
"reason": "<string>",
"code": "dangling_reference",
"identifier": "<string>",
"referenceType": "block",
"missingId": "<string>",
"firstUsedAt": "<string>",
"conflictsWith": "<string>"
}
]
}{
"title": "<string>",
"status": 123,
"detail": "<string>",
"requestId": "<string>",
"type": "<string>",
"instance": "<string>",
"code": "ai_features_not_enabled",
"details": {},
"invalid_params": [
{
"name": "<string>",
"reason": "<string>",
"code": "dangling_reference",
"identifier": "<string>",
"referenceType": "block",
"missingId": "<string>",
"firstUsedAt": "<string>",
"conflictsWith": "<string>"
}
]
}{
"title": "<string>",
"status": 123,
"detail": "<string>",
"requestId": "<string>",
"type": "<string>",
"instance": "<string>",
"code": "ai_features_not_enabled",
"details": {},
"invalid_params": [
{
"name": "<string>",
"reason": "<string>",
"code": "dangling_reference",
"identifier": "<string>",
"referenceType": "block",
"missingId": "<string>",
"firstUsedAt": "<string>",
"conflictsWith": "<string>"
}
]
}{
"title": "<string>",
"status": 123,
"detail": "<string>",
"requestId": "<string>",
"type": "<string>",
"instance": "<string>",
"code": "ai_features_not_enabled",
"details": {},
"invalid_params": [
{
"name": "<string>",
"reason": "<string>",
"code": "dangling_reference",
"identifier": "<string>",
"referenceType": "block",
"missingId": "<string>",
"firstUsedAt": "<string>",
"conflictsWith": "<string>"
}
]
}Authorizations
NextAuth session JWT cookie. Development: often next-auth.session-token.
Production (HTTPS): often __Secure-next-auth.session-token. Send the cookie your browser receives after sign-in.
Query Parameters
Workspace identifier. This is the canonical container ID for v3 APIs.
Page size. A value above the maximum is rejected with 400 rather than silently reduced to it — v3 rejects unusable input instead of substituting its own, the same way it rejects unrecognized query parameters. The server may still return fewer items than requested, so treat meta.nextCursor as the only end-of-collection signal rather than comparing the page length against limit.
1 <= x <= 250Opaque cursor returned as meta.nextCursor from the previous page. Omit on the first request.
Optional survey scoping. Single-valued deliberately: an [in] form would defeat the ordered index scan that makes this the fast path.
Optional contact scoping. Returns only responses submitted by this contact.
Whether to compute meta.totalCount. Defaults to false here, unlike the survey list, because responses are the highest-cardinality table in the product and the extra count is not free at that size. When false, both meta.totalCount and meta.totalCountRelation are null.
When true, the count is capped at 10 000 and meta.totalCountRelation says whether the figure is exact (eq) or a lower bound (gte). If all you need is the count, use GET /api/v3/responses/count rather than fetching a page to get it — and if you need an exact figure beyond the cap, that endpoint takes an explicit precision=exact.
Only records created at or after this instant (inclusive). Mutually exclusive with filter[createdAt][gt] — sending both is a 400.
Only records created strictly after this instant (exclusive). Paired with filter[createdAt][lt] this is a fully open (start, end) window. For a half-open [start, end) window — the one whose consecutive pages tile without dropping a record that sits exactly on a boundary instant — use filter[createdAt][gte] with filter[createdAt][lt]. Mutually exclusive with filter[createdAt][gte] — sending both is a 400.
Only records created at or before this instant (inclusive). Mutually exclusive with filter[createdAt][lt] — sending both is a 400.
Only records created strictly before this instant (exclusive). The upper bound of a half-open [start, end) window, which is what makes consecutive windows tile without overlapping or dropping a record on the boundary. Mutually exclusive with filter[createdAt][lte] — sending both is a 400.
Completed (true) or partial (false) responses only. Omit for both. Requires surveyId or contactId.
Survey language codes to include. Repeat the parameter or use comma-separated values. Values are survey language codes rather than BCP-47 locales — default is legitimate. Requires surveyId or contactId.
Fetch a specific set of responses by id, at most 100 per call. Ids outside the requested scope are simply absent from the result rather than erroring, so this cannot be used to probe whether an id exists elsewhere.
100Sort order. -createdAt (newest first) is the default; createdAt sorts oldest first, which is what a consumer doing an incremental backfill wants. The leading - marks descending.
updatedAt is not offered: it has no index, no product usage, and being mutable it cannot anchor a keyset cursor.
-createdAt, createdAt Response
Responses retrieved successfully
Was this page helpful?