# Reports API - Contact Activity

Use the contact activity report to retrieve one contact's email history: which messages they were sent and what they did with each one (delivered, bounced, opened, clicked which links, unsubscribed), with a summary across all of them.

---

## Endpoints

| Method | URL | Description |
|---|---|---|
| GET | `https://e1.envoke.com/v2/reports/contacts/{ID}` | Return the activity of the contact with this Envoke contact ID. |
| GET | `https://e1.envoke.com/v2/reports/contacts?email={email}` | Return the activity of the contact with this email address. |
| GET | `https://e1.envoke.com/v2/reports/contacts?remote_id={remote_id}` | Return the activity of the contact with this remote ID. |

Authentication is via HTTP Basic Auth using your API ID and API Key.

---

## Get a Contact's Activity

```
GET https://e1.envoke.com/v2/reports/contacts/{ID}
```

Returns one JSON object with three parts: `contact` (who), `summary` (counts across every message), and `messages` (one entry per message, newest first, paged with `limit` and `skip`). Replace `{ID}` with the Envoke contact ID, the `id` of the [Contact API](https://envoke-developers.pages.dev/v1/contacts/fields/).

A contact that does not exist, belongs to another account, or has been deleted returns `[]` with HTTP 200. A contact who has never been sent a message returns the object with every count `0`, every time `null`, and `messages` `[]`.

This report covers email messages only: form submissions and other contact events are not included.

**Accounts restricted from contact-level email activity.** On an account restricted from seeing contacts associated with email activity (the setting that hides a contact's Email sending history in Envoke), this endpoint and [Find a Contact's Activity](#find-a-contacts-activity-by-email-or-remote-id) return HTTP 403; see [Errors](#errors). The message reports are not affected.

### Query Parameters

| Parameter | Type | Default | Description |
|---|---|---|---|
| `activity_from` | string | — | Count only activity on or after this date or time. See [Date parameters](https://envoke-developers.pages.dev/v2/reports/fields/#date-parameters). |
| `activity_to` | string | — | Count only activity on or before this date or time. |
| `limit` | integer | `10` | Maximum number of entries in `messages`; values above `100` return 100. |
| `skip` | integer | `0` | Number of `messages` entries to skip. |

`limit` and `skip` apply only to the `messages` array; `contact` and `summary` are always complete. Any other parameter, including `sent_from` and `sent_to`, returns HTTP 400 (`Parameter 'sent_from' is not valid`).

---

## Find a Contact's Activity by Email or Remote ID

```
GET https://e1.envoke.com/v2/reports/contacts?email={email}
GET https://e1.envoke.com/v2/reports/contacts?remote_id={remote_id}
```

Returns the same object as [Get a Contact's Activity](#get-a-contacts-activity) for the contact found by `email` or by `remote_id`. Give exactly one of the two, plus any of `activity_from`, `activity_to`, `limit`, and `skip`. URL-encode the value (`@` is `%40`).

The value is compared as a whole, exactly as the Contact API's `filter[email]` and `filter[remote_id]` compare it, so a value the Contact API finds is found here. Letter case does not matter: `MAYA.CHEN@EXAMPLE.ORG` finds the contact `maya.chen@example.org`, and a remote ID is matched the same way. An empty value is an error rather than a match for every contact without an email address or remote ID. When no contact matches, the response is `[]` with HTTP 200. When more than one contact matches, the request fails with HTTP 400 rather than choosing one; use the contact's `id` instead. More than one match is possible for `remote_id`, and for `email` on accounts that allow several contacts to share an address.

---

## Response

### `contact`

| Field | Type | Description |
|---|---|---|
| `id` | string | Envoke contact ID. |
| `email` | string | The contact's email address. |
| `remote_id` | string | The contact's remote ID, or `""` when it has none. |

### `summary`

Counts of **messages**, across every message in the report (not only the current page of `messages`), inside the activity window when one is given:

| Field | Type | Description |
|---|---|---|
| `sends` | integer | Messages the contact was sent. |
| `delivered` | integer | Messages with `delivered` `true`. |
| `bounces` | integer | Messages with `bounced` `true`. |
| `opens` | integer | Messages with `opened` `true`. |
| `clicks` | integer | Messages with `clicked` `true`. |
| `unsubscribes` | integer | Messages with `unsubscribed` `true`. |
| `last_send_time` | string or null | The most recent time the contact was sent a message. |
| `last_open_time` | string or null | The most recent open record. |
| `last_click_time` | string or null | The most recent click record. |

Envoke records a contact's first open of a message in each email client and their first click on each link, never a repeat (see [Times and time zone](https://envoke-developers.pages.dev/v2/reports/fields/#times-and-time-zone)). So `last_open_time` is the latest of those first opens: a contact who reopens an old message in the same email client does not move it, but opening a new message, or the same message in another client, does. `last_click_time` works the same way for links.

### `messages`

One entry per message the contact was sent, ordered by `sent_time`, newest first, then by message `id`, highest first. A message is included when the contact has a send record for it and, when an activity window is given, at least one activity record for it inside the window. Messages the contact was selected for but skipped (no consent, invalid address) are not included. A message is listed whatever its status today, as on the contact page: a transactional message, or a message sent and later returned to draft, appears here even though the message reports do not answer for it.

| Field | Type | Description |
|---|---|---|
| `id` | string | Message ID; the Message API `id`. |
| `name` | string | The message's internal name. |
| `sent_time` | string | When this contact was sent the message. For an automated message, the time of this contact's send, not the message's set-up date. |
| `delivered` | boolean | `true` when the contact was sent the message and has no bounce record for it. With an activity window, the send must be inside the window; a bounce counts whenever it arrived. |
| `bounced` | boolean | `true` when the contact has any bounce record for the message, including a temporary deferral. |
| `opened` | boolean | `true` when the contact opened the message. |
| `first_open_time` | string or null | The contact's first open of the message, or `null`. |
| `opens` | integer | The number of email clients the contact opened the message in; not the number of times they opened it. |
| `clicked` | boolean | `true` when the contact clicked any tracked link in the message. |
| `clicks` | integer | The number of different links the contact clicked; not the number of clicks. |
| `links` | array | `{ "id", "url", "first_click_time" }` for each link the contact clicked, most recent first. |
| `unsubscribed` | boolean | `true` when the contact revoked consent or used one-click unsubscribe from the message (the events counted in [`unsubscribes`](https://envoke-developers.pages.dev/v2/reports/fields/#unsubscribes)). |

In `links`, `id` is the tracked link's ID (the same as in the single-message report's [`links`](https://envoke-developers.pages.dev/v2/reports/message-stats/#links)) and `url` is the link as written in the message with Envoke's tracking parameters removed, or `""` for Envoke's own links (view online, preferences, poll answers). Merge tags in a link are not replaced, so `url` can be or contain a merge tag (`{@customfield-url_test}`, `https://example.org/show?seat={@seat|url}`) rather than a valid URL. `first_click_time` is the contact's first click on that link. For a message with an anonymous poll, poll-answer clicks are left out entirely, as in the Sent message report's drill-downs: they are not listed in `links`, not counted in `clicks`, `clicked`, or the `summary` (`clicks` and `last_click_time`), and do not by themselves place the message inside an activity window.

**Activity window.** With `activity_from` or `activity_to`, every flag and count reflects only the records inside the window, except `sent_time`, which is always the contact's send. A contact sent the message before the window who opened it inside the window shows `opened` `true` and `delivered` `false`, because the send is outside the window. `delivered` follows the message-level [`delivered`](https://envoke-developers.pages.dev/v2/reports/fields/#delivered): with a window it needs a send inside the window and no bounce for the message at any time.

**Relation to the contact page.** Each entry matches the row for the same message in the contact's Email sending history in Envoke (`https://e1.envoke.com/contacts/{id}/delivery-history.json`, `.campaigns[] | {campaign_id, flags}`): `bounced`, `opened`, and `clicked` equal its `bounce`, `open`, and `click` flags, except that on a message with an anonymous poll a contact whose only clicks were poll answers has `clicked` `false` where the page shows a click. A message the page lists from opens or clicks alone, with no send record for the contact, is not in this report. The app and this report both show times in your account's time zone.

### Response

```json
{
  "contact": {
    "id": "1004",
    "email": "maya.chen@example.org",
    "remote_id": "MRCT-0412"
  },
  "summary": {
    "sends": 3,
    "delivered": 3,
    "bounces": 0,
    "opens": 2,
    "clicks": 1,
    "unsubscribes": 0,
    "last_send_time": "2026-05-14 09:31:07",
    "last_open_time": "2026-05-14 12:02:44",
    "last_click_time": "2026-05-14 12:03:10"
  },
  "messages": [
    {
      "id": "2004",
      "name": "Pirates Opening Night",
      "sent_time": "2026-05-14 09:31:07",
      "delivered": true,
      "bounced": false,
      "opened": true,
      "first_open_time": "2026-05-14 12:02:44",
      "opens": 1,
      "clicked": true,
      "clicks": 1,
      "links": [
        { "id": "88125", "url": "https://example.org/tickets/pirates", "first_click_time": "2026-05-14 12:03:10" }
      ],
      "unsubscribed": false
    },
    {
      "id": "2001",
      "name": "Spring Season Announcement",
      "sent_time": "2026-04-15 10:00:52",
      "delivered": true,
      "bounced": false,
      "opened": true,
      "first_open_time": "2026-04-15 10:04:19",
      "opens": 2,
      "clicked": false,
      "clicks": 0,
      "links": [],
      "unsubscribed": false
    },
    {
      "id": "2006",
      "name": "Welcome to Maple Ridge",
      "sent_time": "2025-11-03 08:00:12",
      "delivered": true,
      "bounced": false,
      "opened": false,
      "first_open_time": null,
      "opens": 0,
      "clicked": false,
      "clicks": 0,
      "links": [],
      "unsubscribed": false
    }
  ]
}
```

---

## Errors

Invalid requests return HTTP 400 with the validation envelope (see [Error Responses](https://envoke-developers.pages.dev/v2/reports/fields/#error-responses)). Besides the shared date, `limit`, `skip`, and unknown-parameter errors:

| Error | Cause |
|---|---|
| `One of the parameters 'email' or 'remote_id' must be provided` | `GET /v2/reports/contacts` without either. |
| `Only one of the parameters 'email' or 'remote_id' may be provided` | Both were given. |
| `Invalid 'email': ''` | `email` (or `remote_id`: `Invalid 'remote_id': ''`) was given with an empty value. |
| `More than one contact found for 'remote_id': 'MRCT-0412'` | Several contacts have this remote ID (or, with `email`, this address: `More than one contact found for 'email': 'maya.chen@example.org'`). |
| `Parameter 'email' is not valid` | `email` or `remote_id` given on `GET /v2/reports/contacts/{ID}`, where the path already names the contact. |

| Response | Cause |
|---|---|
| `[]` with HTTP 200 | The contact does not exist, belongs to another account, or is deleted; or no contact has this `email` or `remote_id`. |
| HTTP 404, `404 Not Found` | `{ID}` is not a number. |
| HTTP 403, `Contact-level email activity is not available on this account` | The account is restricted from seeing contacts associated with email activity. Returned by both contact activity endpoints. |

The 403 body:

```json
{
  "success": false,
  "result_text": "Contact-level email activity is not available on this account",
  "result_data": []
}
```

---

## Examples

### One contact's activity, ten most recent messages

```bash
curl -X GET -gs -u "$API_ID:$API_KEY" \
  "https://e1.envoke.com/v2/reports/contacts/1004"
```

### By email address

```bash
curl -X GET -gs -u "$API_ID:$API_KEY" \
  "https://e1.envoke.com/v2/reports/contacts?email=maya.chen%40example.org"
```

### By remote ID, activity in May

```bash
curl -X GET -gs -u "$API_ID:$API_KEY" \
  "https://e1.envoke.com/v2/reports/contacts?remote_id=MRCT-0412&activity_from=2026-05-01&activity_to=2026-05-31"
```

Response (only 2004 had activity for this contact in May):

```json
{
  "contact": {
    "id": "1004",
    "email": "maya.chen@example.org",
    "remote_id": "MRCT-0412"
  },
  "summary": {
    "sends": 1,
    "delivered": 1,
    "bounces": 0,
    "opens": 1,
    "clicks": 1,
    "unsubscribes": 0,
    "last_send_time": "2026-05-14 09:31:07",
    "last_open_time": "2026-05-14 12:02:44",
    "last_click_time": "2026-05-14 12:03:10"
  },
  "messages": [
    {
      "id": "2004",
      "name": "Pirates Opening Night",
      "sent_time": "2026-05-14 09:31:07",
      "delivered": true,
      "bounced": false,
      "opened": true,
      "first_open_time": "2026-05-14 12:02:44",
      "opens": 1,
      "clicked": true,
      "clicks": 1,
      "links": [
        { "id": "88125", "url": "https://example.org/tickets/pirates", "first_click_time": "2026-05-14 12:03:10" }
      ],
      "unsubscribed": false
    }
  ]
}
```

### A contact with a bounce

```bash
curl -X GET -gs -u "$API_ID:$API_KEY" \
  "https://e1.envoke.com/v2/reports/contacts/1007?limit=1"
```

Response:

```json
{
  "contact": {
    "id": "1007",
    "email": "r.osei@example.org",
    "remote_id": ""
  },
  "summary": {
    "sends": 2,
    "delivered": 1,
    "bounces": 1,
    "opens": 1,
    "clicks": 0,
    "unsubscribes": 0,
    "last_send_time": "2026-03-10 18:00:41",
    "last_open_time": "2025-09-02 08:47:15",
    "last_click_time": null
  },
  "messages": [
    {
      "id": "2002",
      "name": "Auditions Open",
      "sent_time": "2026-03-10 18:00:41",
      "delivered": false,
      "bounced": true,
      "opened": false,
      "first_open_time": null,
      "opens": 0,
      "clicked": false,
      "clicks": 0,
      "links": [],
      "unsubscribed": false
    }
  ]
}
```

`summary` covers both messages; `limit=1` returns only the newest in `messages`.

---

*Note: The curl examples above are formatted for readability. All requests must use properly encoded URLs.*

*Full reference, OpenAPI description, and Postman collection: [developers.envoke.com](https://developers.envoke.com).*
