Find shopper profiles by what they answered
const url = 'https://api.octaneai.com/v1/profiles/search?limit=20';const options = { method: 'POST', headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'}, body: '{"groups":[{"all":[{"component_key":"image_choice-48su5","kind":"choice","op":"in","option_labels":["Dry"],"page_key":"skin","quiz_id":"quiz_7c9e6679742540de944be07fc1f90ae7"},{"component_key":"number_input-re853","kind":"number","max":50,"min":30,"op":"between","page_key":"skin","quiz_id":"quiz_7c9e6679742540de944be07fc1f90ae7"}]},{"all":[{"dimension_id":"dry","kind":"points","op":"gt","quiz_id":"quiz_7c9e6679742540de944be07fc1f90ae7","value":10}]}],"match":"any_session"}'};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}curl --request POST \ --url 'https://api.octaneai.com/v1/profiles/search?limit=20' \ --header 'Authorization: Bearer <token>' \ --header 'Content-Type: application/json' \ --data '{ "groups": [ { "all": [ { "component_key": "image_choice-48su5", "kind": "choice", "op": "in", "option_labels": [ "Dry" ], "page_key": "skin", "quiz_id": "quiz_7c9e6679742540de944be07fc1f90ae7" }, { "component_key": "number_input-re853", "kind": "number", "max": 50, "min": 30, "op": "between", "page_key": "skin", "quiz_id": "quiz_7c9e6679742540de944be07fc1f90ae7" } ] }, { "all": [ { "dimension_id": "dry", "kind": "points", "op": "gt", "quiz_id": "quiz_7c9e6679742540de944be07fc1f90ae7", "value": 10 } ] } ], "match": "any_session" }'Authorizations
Section titled “Authorizations”Parameters
Section titled “Parameters”Query Parameters
Section titled “Query Parameters”Request Bodyrequired
Section titled “Request Bodyrequired”Who to find: the list filters of GET /v1/profiles except q, plus person facts and an answer filter, an OR of AND groups. Send the same body with every page: a cursor belongs to the search that answered it and is refused with any other body.
object
object
Every slot must hold: a term, or {any: [terms]}; at most 8 terms in the group, counting the terms inside any
object
Choice | number | date | text | email | phone: a question of the quiz (page_key + component_key); points: a dimension (dimension_id); formula: a formula component (page_key + component_key); winner: the top dimension (dimension ids in option_ids); session: how the shopper’s session ended (value completed | abandoned, page_key the page they left on)
Choice: in | not_in; number, points, formula: eq | lt | gt | between; date: eq | before | after | between; text: contains | equals | matches | answered | not_answered; email, phone: answered | not_answered; winner: in; session: equals. not_in means answered the question and picked none of these; not_answered means took the quiz and left the question unanswered.
The quiz the term reads
object
At least one of these terms must hold; plain terms only
object
Choice | number | date | text | email | phone: a question of the quiz (page_key + component_key); points: a dimension (dimension_id); formula: a formula component (page_key + component_key); winner: the top dimension (dimension ids in option_ids); session: how the shopper’s session ended (value completed | abandoned, page_key the page they left on)
Choice: in | not_in; number, points, formula: eq | lt | gt | between; date: eq | before | after | between; text: contains | equals | matches | answered | not_answered; email, phone: answered | not_answered; winner: in; session: equals. not_in means answered the question and picked none of these; not_answered means took the quiz and left the question unanswered.
The quiz the term reads
Any_session: any time they took the quiz counts; latest_completed_per_quiz: only their latest completed take of each quiz
Responses
Section titled “Responses”One page of the matching profiles, newest last-seen first; total is exact up to 5,000 and total_capped says when it stopped there.
object
The profiles, newest last seen first
object
How many of them completed
The profile
The latest moment the shopper was seen
Orders linked to the shopper’s quiz sessions
The quizzes the shopper took
object
The quiz
Its name
True once the shopper’s data was erased on a data-deletion request: no identities or contact values remain
How many quiz sessions the shopper had
Whether another page follows
How many profiles match
A search stops counting at 5,000: total is then that cap, read it as 5,000+
Example
{ "data": [ { "completed_count": 1, "email": "shopper@example.com", "first_seen_at": "2026-09-01T12:00:00Z", "id": "prof_50e1b7e458f14dfca7a2a5b971af2821", "last_order_at": "2026-09-01T12:40:00Z", "last_seen_at": "2026-09-01T12:00:00Z", "orders_count": 1, "phone": "+14155552671", "quizzes": [ { "id": "quiz_7c9e6679742540de944be07fc1f90ae7", "name": "Find your routine" } ], "removed": false, "returning": false, "sessions_count": 1, "shopify_customer_id": "gid://shopify/Customer/7208153301032", "spent": { "amount": "79.97", "currency": "USD" } } ], "has_more": false, "returning_total": 0, "total": 1, "total_capped": false}The key is missing, malformed, revoked or expired.
The body of every non-2xx response.
object
What went wrong, for people
object
The input that failed, as a dotted path (body.label, query.from)
Why it failed
The same value as the Request-Id header; quote it to support
The HTTP status of the response
The machine-readable error code; the Errors guide lists every one
A stable URI naming the error, on this API’s public host
Example
{ "detail": "The API key is missing, malformed, revoked or expired", "request_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7", "status": 401, "title": "invalid_api_key", "type": "https://api.octaneai.com/errors/invalid_api_key"}Headers
Section titled “Headers”Example
BearerThe store’s plan has no API access, or the key lacks the scope.
The body of every non-2xx response.
object
What went wrong, for people
object
The input that failed, as a dotted path (body.label, query.from)
Why it failed
The same value as the Request-Id header; quote it to support
The HTTP status of the response
The machine-readable error code; the Errors guide lists every one
A stable URI naming the error, on this API’s public host
Examples
{ "detail": "The API key does not carry the scope this route needs", "request_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7", "required": [ "profiles:read" ], "status": 403, "title": "insufficient_scope", "type": "https://api.octaneai.com/errors/insufficient_scope"}{ "detail": "The merchant API is available on the Plus and Enterprise plans", "request_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7", "status": 403, "title": "plan_required", "type": "https://api.octaneai.com/errors/plan_required"}Not found
The body of every non-2xx response.
object
What went wrong, for people
object
The input that failed, as a dotted path (body.label, query.from)
Why it failed
The same value as the Request-Id header; quote it to support
The HTTP status of the response
The machine-readable error code; the Errors guide lists every one
A stable URI naming the error, on this API’s public host
Example
{ "detail": "Not found", "request_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7", "status": 404, "title": "not_found", "type": "https://api.octaneai.com/errors/not_found"}A request parameter is invalid.
The body of every non-2xx response.
object
What went wrong, for people
object
The input that failed, as a dotted path (body.label, query.from)
Why it failed
The same value as the Request-Id header; quote it to support
The HTTP status of the response
The machine-readable error code; the Errors guide lists every one
A stable URI naming the error, on this API’s public host
Example
{ "detail": "A request parameter is invalid.", "request_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7", "status": 422, "title": "validation_error", "type": "https://api.octaneai.com/errors/validation_error"}The key’s or the store’s bucket is full; Retry-After says when to try again.
The body of every non-2xx response.
object
What went wrong, for people
object
The input that failed, as a dotted path (body.label, query.from)
Why it failed
The same value as the Request-Id header; quote it to support
The HTTP status of the response
The machine-readable error code; the Errors guide lists every one
A stable URI naming the error, on this API’s public host
Example
{ "detail": "Too many requests", "request_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7", "retry_after": 1, "status": 429, "title": "rate_limited", "type": "https://api.octaneai.com/errors/rate_limited"}The request timed out.
The body of every non-2xx response.
object
What went wrong, for people
object
The input that failed, as a dotted path (body.label, query.from)
Why it failed
The same value as the Request-Id header; quote it to support
The HTTP status of the response
The machine-readable error code; the Errors guide lists every one
A stable URI naming the error, on this API’s public host
Example
{ "detail": "The request timed out.", "request_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7", "status": 504, "title": "timeout", "type": "https://api.octaneai.com/errors/timeout"}