---
title: "List people"
description: "Everyone the Contacts page in the app lists: the saved contacts, and every address seen in mail as the sender or a recipient of a thread's newest message, with how many threads each shares with the mailbox and when mail last moved. A person seen in mail and saved is one row. Paging is keyset: `limit` goes up to 100 and defaults to 25, and `nextCursor` goes back as `cursor` while `hasMore` is true."
url: "https://openemail.uk/docs/api/contacts/people"
area: "API"
category: "Mailbox"
---

# List people

Everyone the Contacts page in the app lists: the saved contacts, and every address seen in mail as the sender or a recipient of a thread's newest message, with how many threads each shares with the mailbox and when mail last moved. A person seen in mail and saved is one row. Paging is keyset: `limit` goes up to 100 and defaults to 25, and `nextCursor` goes back as `cursor` while `hasMore` is true.

`GET /contacts/people`

## GET /contacts/people

Everyone the Contacts page in the app lists: the saved contacts, and every address seen in mail as the sender or a recipient of a thread's newest message, with how many threads each shares with the mailbox and when mail last moved. A person seen in mail and saved is one row. Paging is keyset: `limit` goes up to 100 and defaults to 25, and `nextCursor` goes back as `cursor` while `hasMore` is true.

## Example

Needs `contacts:read`. The addresses seen in mail are included only when the key also holds `threads:read`.

**curl**

```
curl "$OE/contacts/people?sort=threads&limit=25" -H "$AUTH"
```

**Response**

```
{
  "object": "list",
  "data": [
    {
      "object": "person",
      "email": "ada@example.com",
      "displayEmail": "Ada@Example.com",
      "name": "Ada Lovelace",
      "saved": true,
      "source": "manual",
      "notes": "Met at the engine demo",
      "photoUrl": null,
      "threads": 42,
      "lastAt": "2026-09-22T16:04:11.000Z",
      "createdAt": "2026-08-01T10:00:00.000Z",
      "updatedAt": "2026-09-01T09:12:00.000Z",
      "blockedBy": null
    },
    {
      "object": "person",
      "email": "offers@shop.example",
      "displayEmail": "offers@shop.example",
      "name": "Shop",
      "saved": false,
      "source": null,
      "notes": null,
      "photoUrl": null,
      "threads": 17,
      "lastAt": "2026-09-21T07:30:00.000Z",
      "createdAt": null,
      "updatedAt": null,
      "blockedBy": { "rule": "shop.example", "list": "blockedDomains" }
    }
  ],
  "hasMore": true,
  "nextCursor": "WyIyMDI2LTA5LTIxVDA3OjMwOjAwLjAwMFoiLDE3XQ",
  "seen": true
}
```

> `sort` is `recent` (the default), newest mail first with saved contacts never seen in mail after them, `name`, by name or else by address and ignoring case, or `threads`, most threads first. Ties fall back to the address, so a walk never repeats or skips a row.

> `q` searches names, addresses and notes. `email` reads one address, which is how to fetch one person's thread count and last mail. `blocked=true` returns only the people the workspace blocklist blocks, whole-domain rules included, and `blockedBy` names the rule on every row.

> Without `threads:read` the rows are the saved contacts alone and `seen` is false, because the rest are read out of the mail. Deleted addresses and the mailbox's own addresses are never listed. The cursor is opaque: pass it back with the same `sort`, `q` and `blocked`, and one that was not handed out is a `400` `invalid_cursor`.
