curl --request POST \
--url https://app.formbricks.com/api/v3/feedbackRecords/search/semantic \
--header 'Content-Type: application/json' \
--header 'x-api-key: <api-key>' \
--data '
{
"query": "complaints about waiting times at the gates",
"tenant_id": "ph7zv3w2u1x5k9d8m4q6c0bn"
}
'import requests
url = "https://app.formbricks.com/api/v3/feedbackRecords/search/semantic"
payload = {
"query": "complaints about waiting times at the gates",
"tenant_id": "ph7zv3w2u1x5k9d8m4q6c0bn"
}
headers = {
"x-api-key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'x-api-key': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
query: 'complaints about waiting times at the gates',
tenant_id: 'ph7zv3w2u1x5k9d8m4q6c0bn'
})
};
fetch('https://app.formbricks.com/api/v3/feedbackRecords/search/semantic', 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/feedbackRecords/search/semantic",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'query' => 'complaints about waiting times at the gates',
'tenant_id' => 'ph7zv3w2u1x5k9d8m4q6c0bn'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"x-api-key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://app.formbricks.com/api/v3/feedbackRecords/search/semantic"
payload := strings.NewReader("{\n \"query\": \"complaints about waiting times at the gates\",\n \"tenant_id\": \"ph7zv3w2u1x5k9d8m4q6c0bn\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("x-api-key", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://app.formbricks.com/api/v3/feedbackRecords/search/semantic")
.header("x-api-key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"query\": \"complaints about waiting times at the gates\",\n \"tenant_id\": \"ph7zv3w2u1x5k9d8m4q6c0bn\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://app.formbricks.com/api/v3/feedbackRecords/search/semantic")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["x-api-key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"query\": \"complaints about waiting times at the gates\",\n \"tenant_id\": \"ph7zv3w2u1x5k9d8m4q6c0bn\"\n}"
response = http.request(request)
puts response.read_body{
"data": [
{
"feedback_record_id": "018e1234-5678-7abc-8ef0-123456789abd",
"score": 0.71,
"field_label": "What could we improve?",
"value_text": "Queues at the north gate were far too long."
},
{
"feedback_record_id": "018e1234-5678-7abc-8ef0-1234567890aa",
"score": 0.64,
"field_label": "Anything else?",
"value_text": "Took 40 minutes to get in. Please open more turnstiles."
}
],
"limit": 10
}Search feedback records semantically
Embeds query and returns the ids of the records in the dataset whose text is closest to it,
with cosine similarity scores. Only records with a non-empty value_text are embedded, so only
those can match. The gateway reads tenant_id from the JSON body to authorize the call.
Requires an API key with read permission (or higher) on a workspace the dataset is
assigned to.
Results carry ids and the embedded text only — fetch a match with
GET /api/v3/feedbackRecords/{id} for the full record. Answers 503 on instances without
an embedding model configured.
curl --request POST \
--url https://app.formbricks.com/api/v3/feedbackRecords/search/semantic \
--header 'Content-Type: application/json' \
--header 'x-api-key: <api-key>' \
--data '
{
"query": "complaints about waiting times at the gates",
"tenant_id": "ph7zv3w2u1x5k9d8m4q6c0bn"
}
'import requests
url = "https://app.formbricks.com/api/v3/feedbackRecords/search/semantic"
payload = {
"query": "complaints about waiting times at the gates",
"tenant_id": "ph7zv3w2u1x5k9d8m4q6c0bn"
}
headers = {
"x-api-key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'x-api-key': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
query: 'complaints about waiting times at the gates',
tenant_id: 'ph7zv3w2u1x5k9d8m4q6c0bn'
})
};
fetch('https://app.formbricks.com/api/v3/feedbackRecords/search/semantic', 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/feedbackRecords/search/semantic",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'query' => 'complaints about waiting times at the gates',
'tenant_id' => 'ph7zv3w2u1x5k9d8m4q6c0bn'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"x-api-key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://app.formbricks.com/api/v3/feedbackRecords/search/semantic"
payload := strings.NewReader("{\n \"query\": \"complaints about waiting times at the gates\",\n \"tenant_id\": \"ph7zv3w2u1x5k9d8m4q6c0bn\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("x-api-key", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://app.formbricks.com/api/v3/feedbackRecords/search/semantic")
.header("x-api-key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"query\": \"complaints about waiting times at the gates\",\n \"tenant_id\": \"ph7zv3w2u1x5k9d8m4q6c0bn\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://app.formbricks.com/api/v3/feedbackRecords/search/semantic")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["x-api-key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"query\": \"complaints about waiting times at the gates\",\n \"tenant_id\": \"ph7zv3w2u1x5k9d8m4q6c0bn\"\n}"
response = http.request(request)
puts response.read_body{
"data": [
{
"feedback_record_id": "018e1234-5678-7abc-8ef0-123456789abd",
"score": 0.71,
"field_label": "What could we improve?",
"value_text": "Queues at the north gate were far too long."
},
{
"feedback_record_id": "018e1234-5678-7abc-8ef0-1234567890aa",
"score": 0.64,
"field_label": "Anything else?",
"value_text": "Took 40 minutes to get in. Please open more turnstiles."
}
],
"limit": 10
}Authorizations
Management API key; must include workspaceId as an allowed workspace with read, write, or manage permission.
Query Parameters
Page size, at most 100. Unlike the limit on GET /api/v3/feedbackRecords, this one is never rejected: the search endpoints clamp instead of answering 400. A value above 100 is reduced to 100, and 0, a negative number or a non-numeric value falls back to the default of 10. Read the limit in the response to see what was actually applied.
Opaque keyset cursor returned as next_cursor by the previous page. Omit on the first request. A malformed cursor answers 400.
Only matches with score >= min_score are returned. Like limit, this value is clamped rather than rejected: above 1 it becomes 1, below 0 it becomes 0, and a non-numeric value falls back to the default of 0.7. That default is conservative — on the embedding model we measured, query→record similarities for genuinely relevant text often landed around 0.55–0.7 — so if a good query returns nothing, lower it (e.g. 0.5) and filter on the returned value_text. Score distributions differ per model, so treat those numbers as an observation rather than a guarantee.
Body
Response
Matches retrieved
Matches ordered by descending score.
100Show child attributes
Show child attributes
The page size that was applied.
Opaque keyset cursor for the next page. Present only when a full page was returned and there may be more results. Pass it back unchanged as cursor.
Was this page helpful?