> ## 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.

# Mark a chat as read

> Sets read_at on the unread inbound messages of a chat and asks the line's device to send read receipts.

* **Credential:** an API key. A trial key returns `401 unauthorized`. See [Read receipts](/guides/conversations#read-receipts).
* **Line-scoped keys:** the list of a [line-scoped key](/authentication#line-scoped-keys) is not applied: such a key acts on any chat of the account.
* **Test keys:** a call with an `isnap_test_` account key acts on the real chat: see [Test keys](/authentication#test-keys).
* **`Idempotency-Key`:** not honored. A repeat marks only the inbound messages received since the previous call.
* **Events:** sends one `message.read` per message marked, with `chat_id` and `read_at`.
* **Personal data:** each `message.read` carries the whole message: its text, the sender's handle and the names of its files. The call marks every inbound message of the chat that iSnap still keeps and that is not marked read, including the messages that an earlier owner of the line received: ignore the events about messages from before you got the line, see [A chat is never deleted](/guides/receiving-messages#a-chat-is-never-deleted). Request this data only when needed, do not copy it into logs, tickets or other tools, and refer to a contact by an id of your own.
* **What reaches the contact:** the line's device marks the Messages conversation read when it supports read receipts. Otherwise nothing reaches the contact, and the call still succeeds. The `status` of the marked messages stays `delivered`.

## Errors

| Status | `error.code` | When |
| - | - | - |
| 404 | `not_found` | The chat is unknown, or its line is not yours. |
| 422 | `sms_read_receipt_not_supported` | The chat's line is not an iMessage line. |
| 422 | `unsupported_handle` | The chat's contact is not a phone number in E.164. |

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


## OpenAPI

````yaml POST /v1/chats/{chat_id}/read
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/{chat_id}/read:
    post:
      summary: Mark a chat as read
      description: >-
        Sets `read_at` to the current time on every inbound message of the chat
        that has none, and asks the line's device to send read receipts to the
        contact. Returns `200` with `read_at` and `marked_count`, the number of
        messages this request marked: 0 when none was unread. Sends one
        `message.read` event for each message it marks, with `chat_id` and
        `read_at`. The `status` of those messages stays `delivered`, and the
        `unread_count` of the chat does not change. Each `message.read` carries
        the whole message: its text, the sender's handle and the names of its
        files. The messages marked include those that an earlier owner of the
        line received and that iSnap still keeps: after you get a line, ignore
        the events about messages from before you got it.


        The call succeeds even when no read receipt reaches the contact: when
        the line has no paired device, and on every shared line, the request to
        the device is dropped. The chat must be on an iMessage line and the
        handle of its contact must be a phone number in E.164: otherwise the API
        returns `422 sms_read_receipt_not_supported` or `422
        unsupported_handle`.


        Accepts an API key. A line-scoped key can call it for any chat of the
        lines you own: its `line_ids` are not checked. Send no body, or an empty
        JSON object.
      parameters:
        - schema:
            type: string
            pattern: ^chat_[0-9A-Za-z]{25}$
            description: 'Chat id: `chat_` followed by 25 letters or digits.'
          required: true
          description: 'Chat id: `chat_` followed by 25 letters or digits.'
          name: chat_id
          in: path
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties: {}
              description: 'An empty object: the request takes no field.'
      responses:
        '200':
          description: The inbound messages of the chat are marked as read.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ChatMarkReadEnvelope'
        '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'
        '403':
          description: >-
            Refused. `error.code` is `forbidden`: the request carries neither an
            API key in its `Authorization` header nor the header
            `X-Requested-With: XMLHttpRequest`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorEnvelope'
        '404':
          description: >-
            Not found. `error.code` is `not_found`: the chat does not exist, or
            its line is not yours.
          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:
    ChatMarkReadEnvelope:
      type: object
      properties:
        success:
          type: boolean
          enum:
            - true
          description: Always `true` on a success response.
        data:
          $ref: '#/components/schemas/ChatMarkRead'
        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.
    ChatMarkRead:
      type: object
      properties:
        chat_id:
          type: string
          description: Id of the chat.
        read_at:
          type: string
          description: The `read_at` set on the messages this request marked.
        marked_count:
          type: integer
          minimum: 0
          description: >-
            Number of inbound messages this request marked as read. 0 when the
            chat had no inbound message without `read_at`.
      required:
        - chat_id
        - read_at
        - marked_count
      description: The result of `POST /v1/chats/{chat_id}/read`.
  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.