> ## Documentation Index
> Fetch the complete documentation index at: https://docs.isnap.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# List chats

> Lists the chats of the lines you own, most recent activity first, with a cursor.

* **Credential:** an API key. A trial key returns `401 unauthorized`.
* **Line-scoped keys:** a [line-scoped key](/authentication#line-scoped-keys) gets only the chats of the lines of its list.
* **Events:** none.
* **Personal data:** `contact_handle` is a phone number or an email address, and it stays after the messages are deleted. Request it only when needed, do not copy it into logs, tickets or other tools, and refer to a contact by an id of your own.
* **Line owner:** a chat belongs to its line: after a line changes owner, the account that owns the line lists its chats, those of earlier owners included. See [A chat is never deleted](/guides/receiving-messages#a-chat-is-never-deleted).
* **Pagination:** a cursor, not tied to the filters. A chat that has a new message during a walk moves to the front of the list, before the cursor, and the walk does not return it. A message that the device reports late can move a chat back, and the walk can then return it twice. Deduplicate on the chat id, then list again with `since` set earlier than the start of your walk by the reporting delay that you accept. See [Cursors](/pagination#cursors).
* **Counters:** `message_count` does not go down when iSnap deletes messages, and `unread_count` counts every inbound message ever received. See [Chats](/guides/receiving-messages#chats).

## Errors

| Status | `error.code` | When |
| - | - | - |
| 400 | `validation_failed` | A malformed `line_id`, `limit`, `since` or `until`. A cursor that cannot be read is not an error: the list restarts from the first page. |

The errors that every operation shares are in [Errors](/errors-and-rate-limits#errors).


## OpenAPI

````yaml GET /v1/chats
openapi: 3.0.0
info:
  title: iSnap API
  version: 2.45.0
  description: >-
    The iSnap API sends and receives iMessage, RCS and SMS on real phone lines,
    manages those lines, and reports events through webhooks. Every message is
    sent to a real recipient, with an `isnap_live_` key and with an
    `isnap_test_` key alike. Retry a `429` after `Retry-After` seconds. Retry a
    `500` or a `503` up to 2 times, with exponential backoff. Do not retry any
    other `4xx`: fix the request. Retry a write only on an operation that honors
    `Idempotency-Key`, with the key of the first attempt. Responses carry
    personal data: handles, names and message text.
servers:
  - url: https://api.isnap.ai
security:
  - bearerAuth: []
paths:
  /v1/chats:
    get:
      summary: List chats
      description: >-
        Lists the chats of the lines you own, most recent activity first. A chat
        groups the messages between one line and one contact: iSnap creates it
        at the first message in either direction and never deletes it, so a chat
        stays listed after its messages are deleted. A chat belongs to its line,
        not to an account: after a line changes owner, by a transfer or by a
        return to the marketplace, the account that owns the line lists its
        chats, those of earlier owners included, and the earlier owner no longer
        sees them. When the new owner marks one of these chats read, it receives
        in `message.read` the inbound messages of the earlier owner that iSnap
        still keeps and that were not marked read. Before you transfer a line or
        let it return to the marketplace, export what you need from its chats.
        Filter with `line_id`, `since` and `until`. The response carries
        `items`, `has_more` and `next_cursor`: pass `next_cursor` as `cursor` to
        get the next page, and stop when `has_more` is `false`: `next_cursor` is
        then `null`. A malformed `cursor` is not an error: the list restarts
        from the first page, and nothing in the response says so. Chats move
        during a walk: a chat that has a new message moves to the front of the
        list, before the cursor, and the walk does not return it, and a message
        that the device reports late can move a chat back, so that the walk
        returns it twice. Deduplicate on `id`, then list again with `since` set
        earlier than the start of your walk by the reporting delay that you
        accept. Accepts an API key. A line-scoped key lists only the chats of
        the lines in its `line_ids`. `contact_handle` is personal data.
      parameters:
        - schema:
            type: string
            pattern: ^line_[0-9A-Za-z]{25}$
            description: >-
              Only the chats of this line (`line_` followed by 25 letters or
              digits).
          required: false
          description: >-
            Only the chats of this line (`line_` followed by 25 letters or
            digits).
          name: line_id
          in: query
        - schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 20
            description: Number of chats per page, 1 to 100. The default is 20.
          required: false
          description: Number of chats per page, 1 to 100. The default is 20.
          name: limit
          in: query
        - schema:
            type: string
            description: >-
              The `next_cursor` of the previous page. Omit it for the first
              page. Send it with the same filters as the request that returned
              it.
          required: false
          description: >-
            The `next_cursor` of the previous page. Omit it for the first page.
            Send it with the same filters as the request that returned it.
          name: cursor
          in: query
        - schema:
            type: string
            format: date-time
            description: >-
              Inclusive lower bound on `last_message_at`: an ISO 8601 timestamp
              with a UTC offset, for example `2026-01-15T14:00:00Z`.
          required: false
          description: >-
            Inclusive lower bound on `last_message_at`: an ISO 8601 timestamp
            with a UTC offset, for example `2026-01-15T14:00:00Z`.
          name: since
          in: query
        - schema:
            type: string
            format: date-time
            description: >-
              Inclusive upper bound on `last_message_at`: an ISO 8601 timestamp
              with a UTC offset, for example `2026-01-15T15:00:00Z`.
          required: false
          description: >-
            Inclusive upper bound on `last_message_at`: an ISO 8601 timestamp
            with a UTC offset, for example `2026-01-15T15:00:00Z`.
          name: until
          in: query
      responses:
        '200':
          description: One page of chats.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ChatListEnvelope'
        '400':
          description: >-
            Invalid request. `error.code` is `validation_failed` when a
            parameter, a header or the body does not match its schema:
            `error.issues` then lists each problem.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
        '401':
          description: No valid credential. `error.code` is `unauthorized`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
        '429':
          description: >-
            Too many requests. `error.code` is `rate_limit_exceeded` when more
            than 100 requests came from the same network address in 60 seconds,
            whatever their API key or account: wait `error.retry_after` seconds.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
        '500':
          description: >-
            Unexpected failure. `error.code` is `internal_error`. An operation
            that takes a JSON body also returns it when the body, sent as
            `application/json`, is not valid JSON. Quote `trace_id` when you
            report it to iSnap.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
      security:
        - bearerAuth: []
components:
  schemas:
    ChatListEnvelope:
      type: object
      properties:
        success:
          type: boolean
          enum:
            - true
          description: Always `true` on a success response.
        data:
          $ref: '#/components/schemas/ChatList'
        trace_id:
          type: string
          description: >-
            Trace id of the request that produced this response: 32 lowercase
            hexadecimal characters. Quote it when you report a problem to iSnap.
        request_id:
          type: string
          description: >-
            Id of the request that produced this response: `req_` followed by 25
            letters or digits.
      required:
        - success
        - data
        - trace_id
        - request_id
    ErrorEnvelope:
      type: object
      properties:
        success:
          type: boolean
          enum:
            - false
          description: Always `false` on an error response.
        data:
          nullable: true
          description: Always `null` on an error response.
        error:
          type: object
          properties:
            status:
              type: integer
              description: The HTTP status of the response.
            code:
              type: string
              description: >-
                Stable machine-readable code that names the refusal. Branch on
                this value.
            message:
              type: string
              description: >-
                Text for a person. It can change between versions: do not branch
                on it.
            retry_after:
              type: integer
              nullable: true
              description: >-
                Seconds to wait before the next attempt. Set on the `429`
                responses that know a delay, where the `Retry-After` header
                carries the same value. When it is above 60 seconds, do not wait
                in a request: schedule the call, or report the time. `null` on
                every other error.
            retry_at:
              type: string
              nullable: true
              description: >-
                ISO 8601 timestamp at which the refusal ends. Set only on `429
                rebind_cap_reached`. `null` on every other error.
            issues:
              type: array
              nullable: true
              items:
                type: object
                properties:
                  path:
                    type: array
                    items:
                      anyOf:
                        - type: string
                        - type: number
                    description: >-
                      Where the problem is: the field names and array indexes
                      that lead to it.
                  message:
                    type: string
                    description: Text for a person.
                  code:
                    type: string
                    description: >-
                      Kind of problem. On `validation_failed`, for example
                      `invalid_type`, `invalid_value`, `invalid_format`,
                      `invalid_union`, `invalid_key`, `invalid_element`,
                      `too_small`, `too_big`, `not_multiple_of`,
                      `unrecognized_keys` or `custom`. The list is open: read
                      `path` and `message`.
                  min:
                    type: integer
                    description: >-
                      Smallest accepted value. Present only on
                      `limit_out_of_bounds`, `mac_quantity_not_a_pack`,
                      `byop_reduction_strands_mac_sessions` and
                      `byop_reduction_strands_byod_lines`.
                  max:
                    type: integer
                    description: >-
                      Largest accepted value. Present only on
                      `limit_out_of_bounds` and
                      `mac_sessions_exceed_byop_lines`, and on the `too_big`
                      issue of an `Idempotency-Key` header longer than 255
                      characters.
                required:
                  - path
                  - message
                  - code
              description: >-
                Problems found in the request, one entry each. Set on most `400
                validation_failed` responses and on the refusals that name a
                field: a refused `handle` of `POST /v1/lookup` has `issues:
                null`. `null` on every other error.
          required:
            - status
            - code
            - message
            - retry_after
            - retry_at
            - issues
          description: >-
            What went wrong. The six fields are always present; `retry_after`,
            `retry_at` and `issues` are `null` when they do not apply.
        trace_id:
          type: string
          description: >-
            Trace id of the request that produced this response: 32 lowercase
            hexadecimal characters. Quote it when you report a problem to iSnap.
        request_id:
          type: string
          description: >-
            Id of the request that produced this response: `req_` followed by 25
            letters or digits.
      required:
        - success
        - data
        - error
        - trace_id
        - request_id
      description: The body of an error response.
    ChatList:
      type: object
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/Chat'
          description: The chats of this page.
        has_more:
          type: boolean
          description: '`true` when another page follows.'
        next_cursor:
          type: string
          nullable: true
          description: Pass it as `cursor` to get the next page. `null` on the last page.
      required:
        - items
        - has_more
        - next_cursor
      description: One page of chats.
    Chat:
      type: object
      properties:
        id:
          type: string
          description: 'Chat id: `chat_` followed by 25 letters or digits.'
          example: chat_5h7FqK2mNpQ8rT3vW9xY1zA4b
        line_id:
          type: string
          pattern: ^line_[0-9A-Za-z]{25}$
          description: Id of the line of the chat.
        contact_handle:
          type: string
          description: >-
            The handle that opened the chat: the E.164 form of `to` when a
            message you sent opened it, or the sender's handle as the line's
            device stores it, a phone number or an email address, when a
            received message opened it. The typing and read operations take only
            a chat whose `contact_handle` is a phone number in E.164. It is
            personal data. When the line receives messages in a group, they are
            recorded in one chat that shows one handle only, the first sender
            seen: nothing marks that chat as a group.
        outbound_number:
          type: string
          nullable: true
          description: >-
            On a shared line, the phone number that serves this contact, or
            `null` when none is assigned: the next message to the contact is
            then given a number. Always `null` on a line that is not a shared
            line.
        last_message_at:
          type: string
          description: >-
            Time of the last message of the chat, sent or received. Equals
            `created_at` while the chat has no message.
        message_count:
          type: integer
          minimum: 0
          description: >-
            Number of messages recorded in the chat since it was created,
            whoever owned the line. It does not decrease when iSnap deletes
            messages at the end of their retention. It decreases by one when
            iSnap deletes the copy of a message sent through the API that the
            device of the line had also reported as typed on the device.
        unread_count:
          type: integer
          minimum: 0
          description: >-
            Number of inbound messages received in the chat since it was
            created. Marking the chat as read does not reset it.
        created_at:
          type: string
          description: >-
            When iSnap created the chat: at the first message in either
            direction.
        updated_at:
          type: string
          description: When the chat last changed.
      required:
        - id
        - line_id
        - contact_handle
        - outbound_number
        - last_message_at
        - message_count
        - unread_count
        - created_at
        - updated_at
      description: >-
        The messages between one line and one contact. iSnap creates a chat at
        the first message in either direction and never deletes it: a chat stays
        listed after its messages are deleted. It uses the same chat for both
        directions when the device of the line stores the handle of the contact
        in the form of the `to` you send to. A chat belongs to its line, not to
        an account: after a line changes owner, the account that owns the line
        lists its chats, those of earlier owners included, and their counters
        include the messages of earlier owners.
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      description: >-
        An API key created in the iSnap dashboard, or a trial key, sent as
        `Authorization: Bearer <key>`. An API key starts with `isnap_live_` or
        `isnap_test_`; both act on the same account, lines and billing. A trial
        key acts on its trial only.

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.