---
title: "Find shopper profiles by what they answered"
method: POST
path: "/v1/profiles/search"
tags: ["profiles"]
---

# Find shopper profiles by what they answered

`POST /v1/profiles/search`

## Request

- Base URL: `https://api.octaneai.com`
- URL: `https://api.octaneai.com/v1/profiles/search`
- Auth: HTTP bearer

## Query parameters

- `cursor` string, nullable: From the previous page's next_cursor
- `limit` integer

## Request body

- ProfileSearch: 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.
  - `quiz_id` string, nullable: Only people who took this quiz
  - `from` string, date, nullable: Last seen on or after this UTC day
  - `to` string, date, nullable: Last seen on or before this UTC day
  - `has_order` boolean, nullable: Placed an order we linked to a quiz session
  - `has_email` boolean, nullable: Has an email
  - `has_phone` boolean, nullable: Has a phone
  - `has_marketing_consent` boolean, nullable: Ticked the marketing-consent checkbox beside their newest email or phone answer
  - `is_returning` boolean, nullable: Took a quiz again after we first knew them, on any device
  - `match` 'any_session' | 'latest_completed_per_quiz': any_session: any time they took the quiz counts; latest_completed_per_quiz: only their latest completed take of each quiz
  - `groups` ProfileSearchGroup[], nullable: OR of AND groups: up to 4 groups of 8 terms
    - `all` union[], required: Every slot must hold: a term, or {any: [terms]}; at most 8 terms in the group, counting the terms inside any
      - union
        - ProfileSearchTerm
          - `quiz_id` string, required: The quiz the term reads
          - `kind` 'choice' | 'number' | 'date' | 'text' | 'email' | 'phone' | 'points' | 'formula' | 'winner' | 'session', required: 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)
          - `op` 'in' | 'not_in' | 'eq' | 'lt' | 'gt' | 'between' | 'before' | 'after' | 'contains' | 'equals' | 'matches' | 'answered' | 'not_answered', required: 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.
          - `page_key` string, nullable: The question's page (choice, number, date, text, email, phone, formula) or the page left on (session)
          - `component_key` string, nullable: The question or formula
          - `dimension_id` string, nullable: points: the dimension
          - `option_ids` string[], nullable: Option ids (exact)
          - `option_labels` string[], nullable: Option labels, matched trim- and case-insensitively against every version of the quiz
          - `min` union: between: the low bound (a number, or a YYYY-MM-DD day)
            - number
            - string
          - `max` union: between: the high bound
            - number
            - string
          - `value` union: eq, lt, gt, before, after, contains, equals: the operand; matches: a case-insensitive regular expression of at most 64 characters with a run of three plain letters or digits, no lookarounds or backreferences; session: completed | abandoned
            - number
            - string
        - ProfileSearchAny
          - `any` ProfileSearchTerm[], required: At least one of these terms must hold; plain terms only
            - `quiz_id` string, required: The quiz the term reads
            - `kind` 'choice' | 'number' | 'date' | 'text' | 'email' | 'phone' | 'points' | 'formula' | 'winner' | 'session', required: 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)
            - `op` 'in' | 'not_in' | 'eq' | 'lt' | 'gt' | 'between' | 'before' | 'after' | 'contains' | 'equals' | 'matches' | 'answered' | 'not_answered', required: 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.
            - `page_key` string, nullable: The question's page (choice, number, date, text, email, phone, formula) or the page left on (session)
            - `component_key` string, nullable: The question or formula
            - `dimension_id` string, nullable: points: the dimension
            - `option_ids` string[], nullable: Option ids (exact)
            - `option_labels` string[], nullable: Option labels, matched trim- and case-insensitively against every version of the quiz
            - `min` union: between: the low bound (a number, or a YYYY-MM-DD day)
              - …
            - `max` union: between: the high bound
              - …
            - `value` union: eq, lt, gt, before, after, contains, equals: the operand; matches: a case-insensitive regular expression of at most 64 characters with a run of three plain letters or digits, no lookarounds or backreferences; session: completed | abandoned
              - …

## Response `200`

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.

- ProfileList
  - `data` ProfileSummary[], required: The profiles, newest last seen first
    - `id` string, required: The profile
    - `removed` boolean, required: True once the shopper's data was erased on a data-deletion request: no identities or contact values remain
    - `removed_at` string, date-time, nullable, required: When the data was erased; null otherwise
    - `email` string, nullable, required: The shopper's email, lowercase; null when none was captured or the profile was removed
    - `phone` string, nullable, required: The shopper's phone in E.164; null when none was captured or the profile was removed
    - `shopify_customer_id` string, nullable, required: The shopper's Shopify customer as a gid://shopify/Customer/<id> GID: known once they took a quiz logged in to the store or placed an order linked to a quiz session; null otherwise
    - `quizzes` ProfileQuiz[], required: The quizzes the shopper took
      - `id` string, required: The quiz
      - `name` string, required: Its name
    - `first_seen_at` string, date-time, nullable, required: The start of the shopper's first session; null when not known
    - `last_seen_at` string, date-time, required: The latest moment the shopper was seen
    - `sessions_count` integer, required: How many quiz sessions the shopper had
    - `completed_count` integer, required: How many of them completed
    - `returning` boolean, nullable, required: True when the shopper took a quiz again after we first knew them, on any device; null on a removed profile
    - `orders_count` integer, required: Orders linked to the shopper's quiz sessions
    - `spent` Money, required
      - `amount` string, required: A decimal string, as "79.97"
      - `currency` string, required: ISO 4217 code
    - `last_order_at` string, date-time, nullable, required: When the latest of them was placed; null without orders
  - `has_more` boolean, required: Whether another page follows
  - `next_cursor` string, nullable, required: Send as cursor for the next page; null on the last
  - `total` integer, required: How many profiles match
  - `returning_total` integer, nullable, required: How many of total came back; null once the total is capped
  - `total_capped` boolean: A search stops counting at 5,000: total is then that cap, read it as 5,000+

## Other responses

- `401`: The key is missing, malformed, revoked or expired.
- `403`: The store's plan has no API access, or the key lacks the scope.
- `404`: Not found
- `422`: A request parameter is invalid.
- `429`: The key's or the store's bucket is full; Retry-After says when to try again.
- `504`: The request timed out.

---

[API](https://skmtc.dev/octaneai/apis/octane-ai-merchant-api.md) - [All operations](https://skmtc.dev/octaneai/apis/octane-ai-merchant-api/llms.txt) - [OpenAPI document](https://skmtc.dev/octaneai/apis/octane-ai-merchant-api/revisions/dc2d1aa2b7c6?raw)
