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

# List contacts

> GET /v1/contacts: find contacts by email address or your own user ID, or list them all

Returns a paginated list of the contacts in your organization, oldest first. A contact is one person who has written to you. Filter by `email` or `externalId` to find a specific person, for example as the first step of [exporting everything about them](https://docs.replyful.com/api-reference/export-contact.md). With no filter you get every contact.

```http theme={null}
GET /v1/contacts
```

## Authentication

Bearer token in the `Authorization` header. A read-only key is enough. See [Authentication](https://docs.replyful.com/api-reference/authentication.md).

```http theme={null}
Authorization: Bearer rfl_live_...
```

## Query parameters

### Pagination

**limit** `integer` (default: 20)

Number of contacts per page. Range `1` to `100`.

**startingAfter** `string`

Opaque cursor from the previous response's `nextCursor`. See [Pagination](https://docs.replyful.com/api-reference/pagination.md).

### Filters

**email** `string`

Only return contacts with this email address. The match is exact and ignores case. It can return more than one contact, see [Several contacts can share an email address](#several-contacts-can-share-an-email-address).

**externalId** `string`

Only return the contact with this `externalId`, your own user ID for the person. The match is exact. An `externalId` is unique, so you get zero or one contact.

### Several contacts can share an email address

Only a verified address is unique. When a visitor merely types an address, for example in the chat widget, Replyful creates a separate contact with `email.verified: false`. A lookup by email can therefore return several contacts. Export each one and judge whether it is the same person.

A contact has exactly one email address. A person who has written from two addresses is two contacts, so look up each address.

Erased contacts are not returned.

## Response

Returns a [list envelope](https://docs.replyful.com/api-reference/pagination.md#response-shape) containing `Contact` objects.

### The Contact object

The fields a ticket's `requester` has carry the same names here.

**id** `string`

Unique identifier, usually prefixed with `con_`.

**object** `string`

Always `"contact"`.

**externalId** `string | null`

Your own user ID for the contact, from the API or [user identification](https://docs.replyful.com/user-identification.md). `null` if none.

**firstName** `string | null`

First name, if known.

**lastName** `string | null`

Last name, if known.

**name** `string | null`

Contact display name.

**email** `object | null`

The contact's email address, or `null` if none is known.

**properties**

**email** `string`

The email address.

**verified** `boolean`

`true` when ownership of the address is proven: verified by the contact or asserted by your own backend via [user identification](https://docs.replyful.com/user-identification.md). `false` for an address the contact merely typed in.

**phone** `string | null`

The contact's phone number as they typed it (for example in the chat handover), or `null`. Not normalised.

**client** `object | null`

The company the contact belongs to, or `null` if none.

**properties**

**id** `string`

Client ID.

**name** `string`

Company name.

**dataFields** `object[]`

The [data fields](https://docs.replyful.com/chat/data-fields.md) remembered on the contact, like a customer number. Empty when none are saved. Values saved on a single ticket are on that ticket instead.

**properties**

**key** `string`

The field's key, as processes mention it (`customer_number`).

**name** `string`

The field's name.

**value** `string`

The value, written in the field's format.

**savedOn** `string`

`ticket` or `contact`, as on a [ticket](https://docs.replyful.com/api-reference/tickets/list.md#the-ticket-object).

**source** `string`

`handover` when the visitor typed it in the handover form, `ai` when the AI read it in the chat.

**updatedAt** `string`

ISO 8601 UTC timestamp of the latest save.

**optedInAt** `string | null`

ISO 8601 UTC timestamp of when the contact accepted the privacy policy in the chat widget. `null` if they have not.

**suppression** `object | null`

Set when Replyful stopped emailing this address. `null` otherwise.

**properties**

**reason** `string`

`bounce` (email could not be delivered) or `complaint` (the recipient marked an email as spam).

**suppressedAt** `string`

ISO 8601 UTC timestamp of when emailing stopped.

**createdAt** `string`

ISO 8601 UTC timestamp of contact creation.

**updatedAt** `string`

ISO 8601 UTC timestamp of the most recent update.

## Examples

### Find contacts by email address

```bash curl theme={null}
curl --get "https://api.replyful.com/v1/contacts" \
  -H "Authorization: Bearer rfl_live_..." \
  --data-urlencode "email=ada@example.com"
```

```ts Node.js theme={null}
const url = new URL("https://api.replyful.com/v1/contacts");
url.searchParams.set("email", "ada@example.com");

const res = await fetch(url, {
  headers: { Authorization: `Bearer ${process.env.REPLYFUL_API_KEY}` },
});
const { data } = await res.json();
```

```json Response theme={null}
{
  "object": "list",
  "data": [
    {
      "id": "con_9dKmT3xWq6bYrN8cJfA2e",
      "object": "contact",
      "externalId": "user_42",
      "firstName": "Ada",
      "lastName": "Lovelace",
      "name": "Ada Lovelace",
      "email": { "email": "ada@example.com", "verified": true },
      "phone": "+46 70 123 45 67",
      "client": { "id": "cli_4mHbW8xKq2eYd7Tf3pLz9", "name": "Analytical Engines Ltd" },
      "dataFields": [
        {
          "key": "customer_number",
          "name": "Customer number",
          "value": "C-10482",
          "savedOn": "contact",
          "source": "handover",
          "updatedAt": "2026-04-29T14:31:10Z"
        }
      ],
      "optedInAt": "2026-04-29T14:29:40Z",
      "suppression": null,
      "createdAt": "2026-03-02T08:15:00Z",
      "updatedAt": "2026-04-29T14:31:10Z"
    },
    {
      "id": "con_p3Tf8xWq2mKb6rNw7Yc4d",
      "object": "contact",
      "externalId": null,
      "firstName": null,
      "lastName": null,
      "name": null,
      "email": { "email": "ada@example.com", "verified": false },
      "phone": null,
      "client": null,
      "dataFields": [],
      "optedInAt": "2026-05-11T10:02:00Z",
      "suppression": null,
      "createdAt": "2026-05-11T10:02:00Z",
      "updatedAt": "2026-05-11T10:04:30Z"
    }
  ],
  "hasMore": false,
  "nextCursor": null,
  "url": "/v1/contacts"
}
```

### Find a contact by your own user ID

```bash theme={null}
curl --get "https://api.replyful.com/v1/contacts" \
  -H "Authorization: Bearer rfl_live_..." \
  --data-urlencode "externalId=user_42"
```

## Error responses

| Status | `error.code` | When |
| - | - | - |
| `401` | `missing_api_key` | The `Authorization` header is absent. |
| `401` | `invalid_api_key` | The token is malformed, unknown, or archived. |
| `422` | `invalid_query_parameter` | A query parameter failed validation. The `param` field names the offender. |
| `422` | `invalid_cursor` | The `startingAfter` cursor could not be decoded. |
| `429` | `too_many_requests` | Rate limit exceeded. Honor `Retry-After`. |

See [Errors](https://docs.replyful.com/api-reference/errors.md) for the full error envelope reference.


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