---
title: "List activity for a contact"
method: GET
path: "/public-api/v1/contacts/{id}/activities"
tags: ["Contacts"]
---

# List activity for a contact

`GET /public-api/v1/contacts/{id}/activities`

## Request

- Base URL: `https://app.hellobonsai.com`
- URL: `https://app.hellobonsai.com/public-api/v1/contacts/{id}/activities`
- Auth: one of:
  - OAuth 2
    - Authorization code: `https://app.hellobonsai.com/oauth/authorize`
    - Token URL: `https://app.hellobonsai.com/oauth/token`
  - HTTP bearer

Lists what happened with a contact, newest first: its own events, the
notes linked to it, and when it was added to or removed from a company.
Company-level activity, such as a company being created or updated, is
listed on the company's activity instead.

`id` identifies the occurrence, not the row: the same email thread,
meeting, note or document send has the same `id` here and on the
activity of each company the contact belongs to. `contact_id` is always
this contact. `company_id` names the company an activity is tied to,
such as the company a contact was added to or a document was sent for,
and is `null` for the rest.

You only see the activity types your role can read. Email subjects and
meeting titles are `null` when the mailbox they came from does not share
them with you.

## Pagination

Pages by `page[number]` like every other list, up to page 40. To read
further back, set `filter[occurred_to]` to the day of the last activity
you read, the date in its `occurred_at`, and start again from page 1;
the activity of that day you already read comes back again. When all 40
pages fall on one day, that day holds more than paging can reach; split
it with `filter[type]`. Activity that arrives while you page shifts
later pages, so the same activity can also appear at the end of one page
and the start of the next. Dedupe by `id`.

## Filtering

- `filter[type]` — activity types or groups, comma separated
  (`invoice.paid`, `email`). A group stands for every type in it that
  your role can read. A type your role cannot read, or a group with no
  type it can, is refused with `403`. Company-level types (`company.*`)
  and `form.sent`, which is recorded on the company, are not on this
  feed and are refused with `400`.
- `filter[occurred_from]` / `filter[occurred_to]` — ISO 8601 dates
  (`2026-10-31`), UTC days, both inclusive.

## Path parameters

- `id` integer, required

## Query parameters

- `page[number]` integer
- `page[size]` integer
- `filter[type]` string
- `filter[occurred_from]` string, date
- `filter[occurred_to]` string, date

## Response `200`

A page of activity.

- object
  - `data` Activity[], required
    - `id` string, required: Identifies the occurrence. An email thread, meeting, note or document send keeps this id wherever it appears, however many contacts it involved. Opaque: do not parse it.
    - `type` 'contact.created' | 'contact.updated' | 'contact.added_to_company' | 'contact.removed_from_company' | 'company.created' | 'company.updated' | 'company.archived' | 'company.restored' | 'document.created' | 'document.sent' | 'document.viewed' | 'document.accepted' | 'document.reminder_sent' | 'invoice.sent' | 'invoice.viewed' | 'invoice.paid' | 'invoice.reminder_sent' | 'note.added' | 'deal.assigned' | 'email.sent' | 'email.received' | 'calendar.event' | 'form.sent' | 'form.submitted' | 'portal.invitation_accepted' | 'portal.message_sent', required: What happened, as `group.action`.
    - `occurred_at` string, date-time, required: When it happened. For an email thread, the time of its latest message; for a meeting, its start.
    - `summary` string, required: One line of plain text describing the activity.
    - `contact_id` integer, nullable, required: The contact the activity is about, or `null` when it is about the company itself. An activity that involved several contacts names one of them.
    - `company_id` integer, nullable, required: The company the activity belongs to: the company a document or invoice was for when it records one, otherwise the company whose activity you are reading.
    - `actor` ActivityActor, required: Who made the activity happen: a member of your team, a contact, or Bonsai itself.
      - `kind` 'member' | 'contact' | 'system', required: Whether a team member, a contact, or Bonsai itself (`system`) did it. A client acting through the client portal or signed in to their own Bonsai account is a `contact`.
      - `id` integer, nullable, required: The `company_member_id` of a team member, or the id of a contact. Deactivated members keep their id. `null` for `system`, for a former team member whose membership no longer exists, and for an email sender who is not a contact.
      - `name` string, required: Display name of whoever did it. `System` when nobody did.
    - `subject` ActivitySubject, required: The record the activity is about, and where to open it in Bonsai.
      - `kind` 'contact' | 'company' | 'document' | 'invoice' | 'note' | 'deal' | 'email' | 'calendar_event' | 'form' | 'project', required: What kind of record the activity is about.
      - `id` union, required: Id of the record in the public API when it has an endpoint there: an integer for documents, invoices, deals, projects, contacts and companies, a UUID for notes. `null` for emails, meetings, forms and legacy documents.
        - number
        - string
      - `title` string, nullable, required: The record title, or `null` when it has none or you may not see it, such as a meeting from a private calendar.
      - `url` string, required: Opens the record in Bonsai. For an email, meeting, form or legacy contract, or a record that no longer exists, it opens the contact page instead, or the company page when there is no contact.
    - `details` union, required: Facts that depend on `type`, or `null` when the type carries none.
      - ActivityPropertyChangeDetails: Which field of a contact or company changed, and its values before and after.
        - `field` string, required: Name of the field that changed.
        - `old_value` string, required: Value before the change, or an empty string. A company industry, size or source reads as its display label.
        - `new_value` string, required: Value after the change, or an empty string. A company industry, size or source reads as its display label.
      - ActivityDocumentDetails: Extra facts about a document activity.
        - `legacy` boolean, required: `true` for a proposal or contract made with the older editors, which the public API does not expose.
      - ActivityInvoiceDetails: The invoice total at the time of the activity.
        - `amount` string, nullable, required: Invoice total as a decimal string, or `null` when it was not recorded.
        - `currency` string, nullable, required: ISO 4217 currency code of the amount.
      - ActivityInvoicePaymentDetails: The invoice total at the time it was paid, and whether the payment was recorded by hand.
        - `amount` string, nullable, required: Invoice total as a decimal string, or `null` when it was not recorded.
        - `currency` string, nullable, required: ISO 4217 currency code of the amount.
        - `manual` boolean, required: `true` when somebody marked the invoice paid by hand instead of it being paid online.
      - ActivityEmailDetails: The email thread subject, when the mailbox it came from lets you see it.
        - `email_subject` string, nullable, required: Subject of the email thread, or `null` when the mailbox it came from doesn't share subjects with you.
      - ActivityCalendarDetails: The state of the meeting.
        - `cancelled` boolean, required: `true` when the meeting was cancelled.
  - `meta` PaginatedMeta, required: Metadata returned alongside every list response.
    - `request_id` string, required: Identifier for this request. Quote it when contacting support about a specific call.
    - `pagination` Pagination, required: Where the returned page sits in the full result set.
      - `page` integer, required: The page that was returned.
      - `page_size` integer, required: How many records this page can hold.
      - `has_more` boolean, required: Whether at least one more page follows this one. Keep incrementing `page[number]` while this is `true`.

## Other responses

- `400`: A filter is unknown or invalid, a pagination parameter is not an integer, or `page[number]` is above 40.
- `401`: The access token is missing, expired or invalid.
- `403`: This token may not read contact activity, or `filter[type]` names a type or group it may not see.
- `404`: No contact with that id in your account. Archived and deleted contacts return `404` too.
- `429`: Rate limit exceeded. Wait at least `Retry-After` seconds before trying again.

---

[API](https://skmtc.dev/hellobonsai/apis/bonsai-api.md) - [All operations](https://skmtc.dev/hellobonsai/apis/bonsai-api/llms.txt) - [OpenAPI document](https://skmtc.dev/hellobonsai/apis/bonsai-api/revisions/abf12ed6a8d3?raw)
