Qualified Rep Conversations API

(Legacy) A visitor's conversations with a human rep, in a self-contained shape. Superseded by the Conversations and Messages endpoints, which are recommended for new integrations, and remains fully supported for existing ones.

Operations 1

GET /v1/rep_conversations List rep conversations #

Work with this as data

Every API here is available over the APIs.io API and to AI agents over MCP.

MCP server

One button, every client — Claude, Cursor, VS Code and the rest.

https://apis.io/mcp

Tools for apis

7 MCP tools reach this
  • find_apisBrowse and filter every API in the catalog.
  • get_api_artifactsOne API's artifacts, grouped by type.
  • get_openapiThe primary OpenAPI for this API.
  • find_similar_apisAPIs that look like this one.
  • apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.
  • resolveTurn a domain, URL or GitHub org into the provider it belongs to.
  • find_cohortsEvery scored population of providers in the catalog.
All 92 tools →

Call it yourself

curl for this page
This API
curl "https://apis.io/api/v1/apis/qualified-com-rep-conversations-api"
All apis
curl "https://apis.io/api/v1/apis?limit=25"

Discovery needs no key. Ratings and market analysis are Pro.

Get an API key

Free tier, no email required.

A second provider on the same verified email joins the account you already have.

OpenAPI Specification

qualified-com-rep-conversations-api-openapi.yml Raw ↑
openapi: 3.2.0
info:
  title: Qualified Enterprise Rep Conversations API
  version: '2.0'
  description: "# Overview\n\n_Last updated: August 9, 2026_\n\nThe Qualified Enterprise API connects your Qualified data to your warehouse, CDP, and downstream systems. It exposes the underlying records for leads, website sessions, conversations, messages, meetings, and emails, so you can build funnels, rep-performance reporting, and engagement metrics on your own terms. It also accepts writes back to Qualified for leads and companies. If you're building a pipeline into Snowflake, Databricks, BigQuery, Adobe AEP, or Eloqua, this is the API for it.\n\nAt the center is the **Lead**, Qualified's rollup of everything known about one identified person: their website activity together with their records in your connected CRMs, keyed on email address. Sessions, conversations, meetings, and emails are **activities**. Sessions, conversations, and meetings each carry a **`visitorId`**, the stable key for the browser the activity happened in, assigned before the person is known and never changed. You resolve those activities to a person through the lead's `visitorIds`. Emails carry a `leadId`; messages join through their `conversationId`.\n\nReads follow a pull model: you query each resource on your own schedule over a time window, re-running to reconcile. Writes cover leads and companies, sent individually or batched through a bulk job, and a single write to the Companies endpoint sets account-level field values that every lead on that domain inherits.\n\nResponses contain personal data, including names, email addresses, and message text. Store and handle it according to your data-protection policies.\n\n# Authentication and access\n\nEvery request is authenticated with a bearer token:\n\n```\ncurl https://api.qualified.com/v2/leads \\\n  -H \"Authorization: Bearer YOUR_API_TOKEN\"\n```\n\nAPI keys are minted in the Qualified app with a specific set of **OAuth scopes**. A key reaches only the endpoints its scopes allow; a request without the required scope returns `403` with `{\"code\": \"insufficient_scope\"}`. Read endpoints require a `:view` scope and write endpoints require a `:manage` scope. Granting `:manage` also grants the matching `:view`.\n\n| Scope | Grants access to |\n|---|---|\n| `lead:view` / `lead:manage` | Read leads / create and update leads |\n| `company:manage` | Create and update companies |\n| `session:view` | Read sessions |\n| `conversation:view` | Read conversations and messages |\n| `meeting:view` / `meeting:manage` | Read meetings / cancel meetings |\n| `email:view` | Read emails |\n| `bulk_job:manage` | Submit bulk jobs and read their status (covers both `/v2/bulk` endpoints) |\n| `gdpr:manage` | Submit GDPR deletion requests |\n| `legacy:view` | Read the legacy bot/rep reporting endpoints |\n\nBoth message endpoints, `/v2/messages` and `/v2/conversations/{id}/messages`, use the `conversation:view` scope. The two field-listing endpoints, `/v2/leads/fields` and `/v2/companies/fields`, require only a valid token.\n\n# Identity\n\nActivities resolve to a person through one of two keys, and this is the model to understand before you design your schema. Sessions, conversations, and meetings are keyed on the **visitor**, the browser the activity happened in. Emails are keyed on the **lead**, since an email is addressed to a person rather than to a browser.\n\n- A **visitor** is a single browser or device. Every session, conversation, and meeting carries a **`visitorId`**, assigned the first time that browser is seen and stable for its lifetime.\n- A **lead** is the person: Qualified's rollup of everything known about one identified individual, keyed on email address and joined to their records in your connected CRMs. Emails carry a **`leadId`**, which is null when the mailing cannot be resolved to a lead.\n\n**A lead owns its visitors.** One person often browses from more than one device, and each device is a separate visitor. Every lead exposes a **`visitorIds`** array naming all the visitors that belong to it. To resolve a session, conversation, or meeting to a person, match its `visitorId` against the lead that lists it. The visitor is the join key, and the lead is where the mapping lives.\n\n**Only identified people are returned as leads.** `/v2/leads` and `/v2/leads/{id}` return leads that have an email address. Activity from a visitor who has not been identified is grouped by its `visitorId` alone, and joins to a lead the moment that visitor appears in a lead's `visitorIds`.\n\n**Unidentified visitors still carry data.** You don't need a lead to learn something about a person. Each session includes a `visitor` object holding that visitor's field answers and CRM record ids, so activity from people who are not yet identified is still worth ingesting. These are the visitor's **current** values, resolved when you make the request, not a snapshot as of that session — re-reading an old session returns today's values. The object is null when the visitor cannot be loaded.\n\n**Identification is retroactive.** When a visitor is identified, it joins the matching lead's `visitorIds`. Because a `visitorId` never changes, the visitor's earlier activity belonged to the same person all along, and you attach it the moment the visitor appears in `visitorIds`.\n\nA few details:\n\n- `visitorId` is present and immutable on every session, conversation, and meeting, including meetings booked outside a website session through a direct or routed meeting link or live booking.\n- Filter sessions, conversations, and meetings with `visitor_id`, and emails with `lead_id`. To pull one person's full history from the visitor-keyed endpoints, read their lead's `visitorIds` and query each in turn. To go the other way, `GET /v2/leads?email=` looks a person up by address.\n- Messages have no identity key of their own. They resolve through their `conversationId`.\n\n# Incremental sync\n\nAn incremental (delta) sync pulls only the records that changed since your last run, rather than re-downloading everything each time. You add a time window to each request: for example, `GET /v2/leads?updated_after=2026-06-01` returns only the leads updated on or after June 1.\n\nThere are three timestamps you can filter on:\n\n- **created:** when the record first appeared. Use it to find brand-new records.\n- **updated:** when the record last changed. Use it to catch new and changed records. Most delta syncs run on this.\n- **ended:** when a session or conversation finished. Sessions and conversations are filtered by when they ended rather than when they were last updated.\n\n**Which timestamp to use for each resource:**\n\n- **Leads, Meetings, Emails** use `updated_after` (with `updated_before` for an upper bound).\n- **Conversations** use `ended_after` / `ended_before`.\n- **Sessions** use `ended_after` / `ended_before`.\n- **Messages** use `created_after` / `created_before`, since messages never change once sent.\n\nMeetings, Emails, and Leads also accept `created_after` / `created_before` if you only want records *created* in the window. Sessions and conversations are windowed by end time only, since both become available once the session ends, so `created_after` / `created_before` are not offered for them. Timestamps are interpreted in **UTC** unless they carry an offset. A bare date like `2026-06-01` means midnight UTC at the *start* of that day on every bound, upper and lower alike, so to cover a whole day set the upper bound to the next day's date or pass a full timestamp. Timestamps in responses always come back as ISO-8601 in UTC (for example, `2026-06-12T15:04:05Z`).\n\nRequest parameters are `snake_case` (`updated_after`); the matching fields in responses are `camelCase` (`updatedAt`). Use `snake_case` in query parameters and read `camelCase` from response bodies.\n\n**What counts as a change.** For the resources you sync on `updated_after`, `updatedAt` advances on:\n\n- **Leads:** an email change, any custom field-value change (including automated updates such as email engagement and unsubscribes), a new visitor being associated with the lead (which is how a late identification surfaces), and a write to the Companies endpoint that changes an account-level field, which advances `updatedAt` on every lead on that domain.\n- **Emails:** recording an open, click, reply, or bounce. `updatedAt` is therefore always at least as recent as `openedAt`, `clickedAt`, `repliedAt`, and `bouncedAt`, so post-send engagement surfaces on `updated_after`. Opens can be recorded well after the send, so keep your re-sync window wide enough to catch them.\n- **Meetings:** any change to the offer or the calendar event, so reschedules, cancellations, and no-shows all surface, not only the original booking.\n\nTwo behaviors to keep in mind:\n\n- **Records become available after a hold.** Sessions, and the conversations and messages in them, appear in list results 30 minutes after the session ends. Leads and emails appear 30 minutes after they are created. Meetings appear 24 hours after they are created, and that hold applies whichever filter you use, so a meeting booked today is not reachable on `updated_after` either until tomorrow. Reads by id are always current. A bound that falls inside the hold has nothing to return yet, so it is rejected with `400` rather than coming back empty: keep `ended_*` (sessions, conversations) and `created_*` (leads, emails, messages, meetings) at or before 30 minutes ago, or 24 hours ago for meetings. Re-run the prior window on your next pass to pick up anything that arrived late.\n- **`updated_*` on `/v2/messages`.** Because messages never change, `updated_after` / `updated_before` on `/v2/messages` behave exactly like `created_after` / `created_before`. `/v2/conversations/{id}/messages` accepts the same pair and filters the stored update time, which for a message that has not been edited matches its creation time.\n\n# Pagination\n\nList endpoints are paged with a cursor, an opaque marker the API returns. Pass back the cursors the API gives you rather than constructing them.\n\n- `after` pages forward and `before` pages backward.\n- Every page returns up to **1000** records; the page size is fixed. (The legacy reporting endpoints page differently — see [Legacy reporting endpoints](#section/Legacy-reporting-endpoints).)\n- Results are ordered newest-first. The exact sort key varies by resource: leads, emails, and meetings sort on their own record id, sessions on their `endedAt`, and conversations and messages on their parent session's `endedAt`. In every case it is a stable descending order, but it is not necessarily the same timestamp you filtered on, so do not infer a record's position from its `updatedAt`.\n\nEvery list response includes a `pageInfo` block:\n\n```json\n{\n  \"data\": [ ... ],\n  \"pageInfo\": {\n    \"hasNextPage\": true,\n    \"hasPreviousPage\": false,\n    \"startCursor\": \"Qx7...\",\n    \"endCursor\": \"Zk2...\"\n  }\n}\n```\n\nPage forward by passing `endCursor` as `after` until `hasNextPage` is `false`. Drive the loop off `hasNextPage` rather than the number of rows you expected, since a page can come back smaller than the maximum. **Pagination, not window size, is how you pull a large range:** request the window you need and page through it, since a wide window never returns everything in one response. For efficient backfills, still work in bounded windows (around a day for high-volume resources) and page each to completion, which keeps every request comfortably within request limits.\n\nCursors are anchored on a record id, so records arriving mid-sync do not shift the page you are on. Two things to handle anyway: a cursor whose record is no longer readable is rejected with `400`, so restart that window rather than retrying the cursor; and `hasPreviousPage` on the activity endpoints (sessions, conversations, messages, meetings) reports only that the current page is non-empty, so drive your loop off `hasNextPage`. Re-running overlapping windows, as described under [Incremental sync](#section/Incremental-sync), is what makes a sync reliable end to end.\n\n# Rate limits\n\nLimits are enforced per team. The defaults:\n\n| Limit | Value |\n|---|---|\n| Concurrent requests | 10 |\n| Requests per 15 minutes | 2,000 |\n| Requests per hour | 7,000 |\n| Requests per day | 120,000 |\n\nResponses carry `RateLimit-Limit`, `RateLimit-Remaining`, and `RateLimit-Reset` (epoch seconds). These describe the **15-minute** window only, so you can have remaining quota there and still be rejected by the hourly or daily tier.\n\nExceeding a limit returns `429` with `{\"code\": \"rate_limited\"}`. For the three time-window limits the response includes a `Retry-After` header; back off for that many seconds. The concurrency limit returns `429` **without** `Retry-After` or `RateLimit-*` headers, so treat a missing `Retry-After` as \"retry once a request in flight completes\" rather than as an error. Keep concurrency at or below 10.\n\n# Errors\n\nThe API uses standard HTTP status codes. Error bodies vary by status code, as shown below; parse each according to the shape listed.\n\n| Status | Meaning | Body | What to do |\n|---|---|---|---|\n| `400` | Malformed request, such as a bad date, cursor, or filter value, or a time window that starts inside a resource's availability hold | `{\"error\": \"...\"}` (bulk: `{\"code\",\"message\"}`) | Fix the request parameters and retry. |\n| `401` | Missing or invalid token, or API not enabled for the team | `{\"code\": \"invalid_token\"}` | Check the token, and that the API is enabled for the team. |\n| `403` | Token lacks the required scope | `{\"code\": \"insufficient_scope\"}` | Re-mint the key with the scope the endpoint needs. |\n| `404` | Record not found, or a path id that can't be decoded | `{\"error\": \"...\"}`, `{\"code\": \"not_found\", \"message\": \"...\"}`, or `{\"message\": \"...\"}` (cancel meeting) | The shape varies by endpoint, so read `error`, `code`, and `message` defensively rather than branching on one key. Treat the record as gone and do not retry. |\n| `422` | Write rejected, such as an unknown field, invalid value, or malformed batch | `{\"error\": \"...\", \"details\": ...}` (bulk: `{\"code\",\"message\"}`) | When a single field is rejected, `details.failed_field` names it; fix that field or value and resubmit. |\n| `429` | Rate limit exceeded | `{\"code\": \"rate_limited\", \"message\": \"...\"}` | Back off for `Retry-After` seconds, then retry. |\n| `500` | Unexpected server error | `{\"error\": \"Internal server error\"}` | Retry with backoff; if it persists, contact support. |\n\n**Bulk writes report failures at two levels.** `POST /v2/bulk` returns `202` to confirm the batch was accepted; individual items within it can still fail. Confirm per-item outcomes by checking `failedRecords` and reading the `errors` on each entry in the job's `result` array.\n\n# Common workflows\n\nCommon integration sequences. Each builds on the concepts above rather than repeating them.\n\n**Backfill history first.** Before your first incremental run, load historical data by walking backward in bounded windows, a week at a time, or a day for high-volume resources, using `created_after`/`created_before` (or `ended_after`/`ended_before` for sessions and conversations) and paging each window to completion. When you reach the present, record the latest timestamp you have seen and switch to the daily delta sync below.\n\n**Daily delta sync.** For each resource, pull a roughly 24-hour window (`updated_after` for leads, meetings, and emails; `ended_after` for conversations and sessions), paging to completion and upserting on the record `id`. For meetings, shift the whole window back 24 hours so it clears the availability hold. Re-run the prior window on the next pass to pick up rows that landed late.\n\n**Resolve identity.** On each leads pull, map the lead's `visitorIds` onto your stored activity so every visitor's history points at the right lead. See [Identity](#section/Identity).\n\n**Pull conversation transcripts.** List conversations on an `ended_after` window, then fetch `/v2/conversations/{id}/messages` for each. `senderType` (`user`, `visitor`, `experience`, `ai_profile`) identifies who spoke, and `senderName` gives the display name where one exists. Both are null when the sender cannot be resolved. Unlike `/v2/messages`, the per-conversation endpoint is not held back, so it returns messages from a session that is still running.\n\n# Versioning and compatibility\n\nThis reference documents `/v2`, the current version and the one to build against. The earlier `/v1` Enterprise API endpoints are still served and remain supported for existing integrations, but they are not documented here. The two legacy reporting endpoints also remain under `/v1` (see [Legacy reporting endpoints](#section/Legacy-reporting-endpoints)). We may add new fields and endpoints over time, so parse responses leniently and ignore any field you do not recognize. Removing a field, changing a field's type, or changing the format of an id or cursor is a breaking change, and we will communicate any such change in advance.\n\n# Legacy reporting endpoints\n\nTwo earlier endpoints, `GET /v1/bot_conversations` and `GET /v1/rep_conversations`, predate the rest of this API and remain under `/v1`. They return a visitor's bot and rep conversations in a self-contained shape, with nested visitor, messages, meetings, and calls, and were the original way to pull conversation history.\n\nThe [Conversations](#tag/Conversations), [Messages](#tag/Messages), and [Meetings](#tag/Meetings) endpoints now cover everything these do and more, in a richer and more consistent form, and are recommended for new integrations. The legacy endpoints remain fully supported for existing integrations. Two differences from the rest of the API: their response fields are **`snake_case`**, predating the `camelCase` convention, and they page with `starting_after`/`limit` and a `meta` block rather than the cursor `pageInfo` used elsewhere.\n\n---"
servers:
- url: https://api.qualified.com
  description: Production
security:
- bearerToken: []
tags:
- name: Rep Conversations
  description: (Legacy) A visitor's conversations with a human rep, in a self-contained shape. Superseded by the Conversations and Messages endpoints, which are recommended for new integrations, and remains fully supported for existing ones.
paths:
  /v1/rep_conversations:
    get:
      summary: List rep conversations
      operationId: listRepConversations
      description: '> **Legacy.** Superseded by the [Conversations](#tag/Conversations), [Messages](#tag/Messages), and [Meetings](#tag/Meetings) endpoints, which cover everything here and more in a richer, more consistent form and are recommended for new integrations. This endpoint remains fully supported for existing integrations.


        Returns a visitor''s conversations with a human rep, each with its nested visitor, rep, messages, meetings, and calls. A rep conversation becomes available once the visitor''s website session ends. Requires the reporting API to be enabled for the team.


        Unlike the rest of this API, responses are `snake_case` and page with `starting_after`/`limit` plus a `meta` block (not the cursor `pageInfo` used elsewhere).


        **Scope:** `legacy:view`

        '
      tags:
      - Rep Conversations
      parameters:
      - name: starting_after
        in: query
        description: Opaque cursor. Pass the `meta.end_cursor` from the previous page. Omit for the first page.
        schema:
          type: string
      - name: limit
        in: query
        description: Maximum number of conversations to return. Defaults to the maximum.
        schema:
          type: integer
          default: 10000
          maximum: 10000
      responses:
        '200':
          description: A page of rep conversations.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/RepConversationResponse'
        '400':
          $ref: '#/components/responses/LegacyBadRequest'
        '401':
          description: Missing or invalid token, the API is not enabled for the team, or the token lacks `legacy:view`. Unlike the rest of this API, an insufficient scope here returns 401 rather than 403.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/CodeErrorResponse'
              example:
                code: invalid_token
        '500':
          $ref: '#/components/responses/InternalServerError'
components:
  schemas:
    CodeErrorResponse:
      type: object
      properties:
        code:
          type: string
          description: Machine-readable error code.
        message:
          type: string
          description: Human-readable error message.
    LegacyVisitor:
      type: object
      description: A visitor, as returned by the legacy reporting endpoints.
      properties:
        id:
          type: string
          description: Encoded visitor id.
        email:
          type:
          - string
          - 'null'
          description: Visitor email, or null if unknown.
        salesforce_lead_id:
          type:
          - string
          - 'null'
          description: Salesforce lead id, returned verbatim (not encoded).
        salesforce_contact_id:
          type:
          - string
          - 'null'
          description: Salesforce contact id, returned verbatim (not encoded).
        pardot_prospect_id:
          type:
          - string
          - 'null'
          description: Pardot prospect id, returned verbatim (not encoded).
        marketo_lead_id:
          type:
          - string
          - 'null'
          description: Marketo lead id, returned verbatim (not encoded).
        hubspot_contact_id:
          type:
          - string
          - 'null'
          description: HubSpot contact id, returned verbatim (not encoded).
        eloqua_contact_id:
          type:
          - string
          - 'null'
          description: Eloqua contact id, returned verbatim (not encoded).
    ErrorResponse:
      type: object
      properties:
        error:
          type: string
          description: Human-readable error message.
    RepConversationResponse:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/RepConversation'
        meta:
          $ref: '#/components/schemas/LegacyPageMeta'
    LegacyMeeting:
      type: object
      description: A meeting within a legacy bot or rep conversation.
      properties:
        offer_sent_at:
          type: string
          format: date-time
          description: When the meeting offer was sent.
        ai:
          type: boolean
          description: True if the meeting was offered by AI.
        booked_user:
          type:
          - object
          - 'null'
          allOf:
          - $ref: '#/components/schemas/LegacyUser'
          description: The rep the meeting was booked with, or null if no meeting was booked.
    LegacyCall:
      type: object
      description: A voice call within a legacy rep conversation.
      properties:
        started_at:
          type: string
          format: date-time
          description: When the call started.
        ended_at:
          type: string
          format: date-time
          description: When the call ended.
    LegacyMessage:
      type: object
      description: A message within a legacy bot or rep conversation.
      properties:
        sent_at:
          type: string
          format: date-time
          description: When the message was sent.
        ai:
          type: boolean
          description: True if the message was sent by AI.
        visitor_id:
          type:
          - string
          - 'null'
          description: Encoded id of the visitor who sent it, or null.
        bot_id:
          type:
          - string
          - 'null'
          description: Encoded id of the bot that sent it, or null.
        user_id:
          type:
          - string
          - 'null'
          description: Encoded id of the rep who sent it, or null.
        text:
          type: string
          description: Message text.
    RepConversation:
      type: object
      description: A visitor's conversation with a human rep (legacy reporting).
      properties:
        id:
          type: string
          description: Encoded rep conversation id.
        started_at:
          type: string
          format: date-time
          description: When the rep conversation started.
        visitor:
          $ref: '#/components/schemas/LegacyVisitor'
        user:
          $ref: '#/components/schemas/LegacyUser'
        bot_conversation_id:
          type:
          - string
          - 'null'
          description: Encoded id of the bot conversation that led to this rep conversation, or null.
        salesforce_lead_captured_by_rep:
          type: boolean
          description: True if the rep created a Salesforce lead for the visitor.
        pardot_prospect_captured_by_rep:
          type: boolean
          description: True if the rep created a Pardot prospect for the visitor.
        marketo_lead_captured_by_rep:
          type: boolean
          description: True if the rep created a Marketo lead for the visitor.
        eloqua_contact_captured_by_rep:
          type: boolean
          description: True if the rep created an Eloqua contact for the visitor.
        meetings:
          type: array
          items:
            $ref: '#/components/schemas/LegacyMeeting'
        calls:
          type: array
          items:
            $ref: '#/components/schemas/LegacyCall'
        messages:
          type: array
          items:
            $ref: '#/components/schemas/LegacyMessage'
    LegacyUser:
      type: object
      description: A rep, as returned by the legacy reporting endpoints.
      properties:
        id:
          type: string
          description: Encoded user id.
        email:
          type: string
          description: Rep email.
        name:
          type: string
          description: Rep name.
    LegacyPageMeta:
      type: object
      description: Pagination metadata for the legacy reporting endpoints.
      properties:
        has_more:
          type: boolean
          description: True if more conversations can be fetched after this page.
        end_cursor:
          type: string
          description: Object id of the last conversation in this page. Pass as `starting_after` to fetch the next page.
  responses:
    LegacyBadRequest:
      description: Invalid `limit` or `starting_after`. The legacy endpoints return a `code`/`message` body rather than the `error` body used elsewhere.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/CodeErrorResponse'
          example:
            code: invalid_limit
            message: ''
    InternalServerError:
      description: Unexpected server error.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorResponse'
          example:
            error: Internal server error
  securitySchemes:
    bearerToken:
      type: http
      scheme: bearer
      bearerFormat: Token
x-tagGroups:
- name: Write APIs
  tags:
  - Leads
  - Companies
  - Bulk
- name: Activity APIs
  tags:
  - Sessions
  - Conversations
  - Messages
  - Meetings
  - Emails
- name: Utility APIs
  tags:
  - Cancel Meeting
  - GDPR
- name: Legacy Reporting API
  tags:
  - Bot Conversations
  - Rep Conversations