# List patient messages

> Lists messages in patient conversations in a time range, newest first, with a cursor. group_by=message (the default) returns them one by one; group_by=conversation returns every message in the range from one conversation before moving to the next. Narrow to one patient or conversation, or search with q. Messages include everyone's in each conversation: the patient, staff, and API keys. With an API key, pass after, patient_id, or conversation_id.

Source: https://www.bloomtext.com/developers/api/reference/list-patient-messages/

`GET /patient-messages`

Lists messages in patient conversations in a time range, newest first, with a cursor. `group_by=message` (the default) returns them one by one; `group_by=conversation` returns every message in the range from one conversation before moving to the next. Narrow to one patient or conversation, or search with `q`. Messages include everyone's in each conversation: the patient, staff, and API keys. With an API key, pass `after`, `patient_id`, or `conversation_id`.

**Credentials:** [API key](https://www.bloomtext.com/developers/api/api-keys/) or an [org admin’s OAuth token](https://www.bloomtext.com/developers/api/oauth/) · **Scope:** `messages.read`

### Query parameters

- `after` (string): Only messages sent at or after this time. An RFC 3339 timestamp, or a date for midnight in the organization's time zone.

- `before` (string): Only messages sent before this time. Same format as `after`.

- `group_by` (message, conversation): `message` (the default) lists messages one by one across conversations. `conversation` lists every message in the range from one conversation, then the next.

- `order` (desc, asc): `desc` for newest first (the default) or `asc` for oldest first. With `group_by=conversation`, it orders the conversations by their latest message and the messages within each.

- `patient_id` (UUID): Only conversations with this patient, on any phone number.

- `conversation_id` (UUID): Only messages in this conversation.

- `q` (string): Words and operators to search for, like `refill from:patient is:unread`. Supports free text, `"exact phrases"`, `from:`, `patient:`, `mrn:`, `phone:`, `is:unread`, `is:failed`, `-` to negate, and `OR`. Use `after` and `before` for time ranges. See Search.

- `page[limit]` (integer): Number of records to return. Defaults to 50, maximum 100.

- `page[after]` (string): Cursor from the previous page's `pagination.next_cursor`.

### Returns

Returns `200 OK` with a `data` array of [Message object](https://www.bloomtext.com/developers/api/reference/message-object/)s and a `pagination` object. See [pagination](https://www.bloomtext.com/developers/api/pagination/).

### Errors

Failed requests return `application/problem+json` [problem details](https://www.bloomtext.com/developers/api/errors/). The errors specific to this endpoint:

| Status | Code | Meaning |
| --- | --- | --- |
| 400 | `invalid_request` | The request is malformed, such as invalid JSON. |
| 400 | `invalid_query` | The `q` query can’t be parsed. See [Search](https://www.bloomtext.com/developers/api/search/). |
| 400 | `range_required` | An API key must pass `after`, `patient_id`, or `conversation_id`, so every read is bounded. |
| 401 | `invalid_token` | The API key or access token is missing, expired, or revoked. |
| 403 | `insufficient_scope` | The credential doesn’t have the scope this endpoint needs. |
| 429 | `rate_limited` | Too many requests. Retry after `Retry-After` seconds. |

```bash filename="Request"
curl "https://api.bloomtext.com/v1/patient-messages?after=2026-09-21T00:00:00Z&q=from:patient" \
  -H "Authorization: Bearer $BLOOMTEXT_API_KEY"
```

```js filename="Request"
const response = await fetch('https://api.bloomtext.com/v1/patient-messages?after=2026-09-21T00:00:00Z&q=from:patient', {
  headers: {
    Authorization: `Bearer ${process.env.BLOOMTEXT_API_KEY}`,
  },
})

if (!response.ok) throw new Error(`BloomText API error: ${response.status}`)
const data = await response.json()
```

```python filename="Request"
import os

import requests

response = requests.get(
    "https://api.bloomtext.com/v1/patient-messages?after=2026-09-21T00:00:00Z&q=from:patient",
    headers={
        "Authorization": f"Bearer {os.environ['BLOOMTEXT_API_KEY']}",
    },
)
response.raise_for_status()
data = response.json()
```

```go filename="Request"
package main

import (
	"fmt"
	"io"
	"net/http"
	"os"
)

func main() {
	req, err := http.NewRequest("GET", "https://api.bloomtext.com/v1/patient-messages?after=2026-09-21T00:00:00Z&q=from:patient", nil)
	if err != nil {
		panic(err)
	}
	req.Header.Set("Authorization", "Bearer "+os.Getenv("BLOOMTEXT_API_KEY"))

	res, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()

	out, _ := io.ReadAll(res.Body)
	fmt.Println(res.Status, string(out))
}
```

```ruby filename="Request"
require "net/http"
require "json"

uri = URI("https://api.bloomtext.com/v1/patient-messages?after=2026-09-21T00:00:00Z&q=from:patient")
request = Net::HTTP::Get.new(uri)
request["Authorization"] = "Bearer #{ENV.fetch("BLOOMTEXT_API_KEY")}"

response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) do |http|
  http.request(request)
end
data = JSON.parse(response.body)
```

```json filename="Response · 200 OK"
{
  "data": [
    {
      "id": "4cbfdb50-6b7d-45d4-94b3-05a52ce3f4d1",
      "conversation": {
        "id": "e5c3b7b8-8f08-4d5a-9af1-0d11b0f4b7a0",
        "title": "Jane Doe"
      },
      "sender": {
        "id": "5a1c9e2b-7d44-4f1a-9c3e-2b8d6f0a1e77",
        "name": "Jane Doe",
        "kind": "patient"
      },
      "patient": {
        "id": "5a1c9e2b-7d44-4f1a-9c3e-2b8d6f0a1e77",
        "name": "Jane Doe"
      },
      "channel": "sms",
      "phone_number": {
        "number": "+15125550100",
        "team": "Front desk"
      },
      "delivery": null,
      "campaign_id": null,
      "created_at": "2026-09-21T15:04:05Z",
      "edited_at": null,
      "type": "text",
      "body": "Hi, can I move my Thursday appointment to Friday morning?",
      "file": null,
      "reply_to_message_id": null,
      "unread": true,
      "sent_via": null
    }
  ],
  "pagination": {
    "next_cursor": null,
    "has_more": false
  }
}
```
