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

# Working with activities

> Create activities and log activity records over the API

This guide shows how to create activities, add attributes to them, log activity records and link those records to your CRM data. If you haven't yet, start with the [activities concept page](/docs/activities) to learn how activities, activity records and schemas relate.

## Required scopes

The activity APIs use two [scopes](/rest-api/guides/authentication):

* `activity_configuration` for reading and writing activities themselves and their attributes.
* `activity_record` for reading and writing activity records.

Activity records link to object records, so the activity record endpoints additionally require `activity_configuration:read`, `object_configuration:read` and `record_permission:read`. Creating referenced records as part of a write also needs `record_permission:read-write`.

## Creating an activity

Create an activity with `POST /v2/activities`. Besides a slug and its display names, you must pick the [schema](/docs/activities#schemas) the activity extends: `activities`, `interactions`, `calls` or `emails`.
When creating custom activities, it's good practice to prefix the slug with an app-specific prefix like `{app_name}_site_visits` to prevent conflicts.

<CodeGroup>
  ```json Create a "Site visits" activity theme={"system"}
  {
    "data": {
      "api_slug": "site_visits",
      "singular_noun": "Site visit",
      "plural_noun": "Site visits",
      "extends": "activities"
    }
  }
  ```
</CodeGroup>

The response contains the new activity, including the schemas it directly extends:

<CodeGroup>
  ```json Response theme={"system"}
  {
    "data": {
      "id": {
        "workspace_id": "14beef7a-99f7-4534-a87e-70b564330a4c",
        "activity_id": "97052eb9-e65e-443f-a297-f2d9a4a7f795"
      },
      "api_slug": "site_visits",
      "singular_noun": "Site visit",
      "plural_noun": "Site visits",
      "extends": [
        {
          "schema_id": "3aeb39cd-fed8-524e-94b8-1300549354ac",
          "schema_slug": "activities"
        }
      ],
      "is_system_activity": false,
      "created_at": "2026-08-14T11:50:00.061281000Z"
    }
  }
  ```
</CodeGroup>

The choice of schema is permanent: `PATCH /v2/activities/{activity}` can update the slug and nouns, but rejects `extends` with a `400`.

You can list all activities with `GET /v2/activities` and fetch a single one with `GET /v2/activities/{activity}`. Wherever `{activity}` appears in a path, you can pass either the activity's UUID or its `api_slug`.

## Deleting an activity

`DELETE /v2/activities/{activity}` deletes an activity **and all of its records**. It needs both `activity_configuration:read-write` and `activity_record:read-write`, and workspace members must be an admin. Archived activities can be deleted too.

<Warning>
  Deleting an activity removes every record logged against it. This cannot be undone.
</Warning>

Only custom activities can be changed. `PATCH` and `DELETE` on a system activity such as `phone_calls` fail with a `400`.

## Adding attributes

The [attribute endpoints](/rest-api/endpoint-reference/attributes) accept `activities` as a target, alongside `objects` and `lists`. For example, to add a "Location" attribute to the site visits activity:

<CodeGroup>
  ```http Create a "Location" attribute theme={"system"}
  POST /v2/activities/site_visits/attributes HTTP/1.1
  Authorization: Bearer <<oauth2>>
  Content-Type: application/json

  {
    "data": {
      "title": "Location",
      "api_slug": "location",
      "type": "location",
      "is_required": false,
      "is_unique": false,
      "is_multiselect": false
    }
  }
  ```
</CodeGroup>

Attributes inherited from the schema (e.g. `timestamp` or `direction`) are listed alongside your custom attributes and look identical in the API. However, they are managed by the schema: attempting to update them or to add select options to them fails with a `400` error explaining that the attribute is inherited.

## Creating activity records

Log an event with `POST /v2/activities/{activity}/records`. The body has the same shape as for [object records](/rest-api/endpoint-reference/records): a `values` map keyed by attribute slug.

The example below logs an outbound renewal call on an activity extending the `calls` schema. It links the Acme company by domain and the participant by email address, without knowing any record IDs:

<CodeGroup>
  ```json Log a renewal call theme={"system"}
  {
    "data": {
      "values": {
        "title": "Renewal call",
        "timestamp": "2026-08-14T11:50:00.000Z",
        "direction": "Outbound",
        "linked_records": [{"target_object": "companies", "domains": [{"domain": "acme.com"}]}],
        "participants": [
          {
            "target_object": "people",
            "email_addresses": [{"email_address": "alice@acme.com"}]
          }
        ]
      }
    }
  }
  ```
</CodeGroup>

A few things to note:

* `timestamp` (when the event happened) is required on every activity record.
* `created_at` and `created_by` are set by the system and cannot be written.
* The response contains the record's composite ID (`workspace_id`, `activity_id`, `record_id`), its `created_at` and all of its `values`. Unlike object records, activity records have no `web_url`.

## Linking records by attribute value

Record reference values such as `linked_records` and `participants` can be written in three formats, just like on object records:

* **ID format**: `{"target_object": "people", "target_record_id": "<uuid>"}`
* **Matching format**: identify the target record by one of its attribute values, e.g. `{"target_object": "people", "email_addresses": [{"email_address": "alice@acme.com"}]}`
* **Target record reference format**: `{"target_object": "people", "target_record_reference": {…}}`, which also says what to do when nothing matches or several records match

The last two are especially useful for activities: integrations that ingest calls or meetings usually know a phone number, email address or domain, not an Attio record ID.

The matching format works with domain, email address, phone number, number and text attributes. On activity record endpoints it behaves as follows:

* **Exactly one record must match.** If a matching value resolves to several records, e.g. a phone number shared by two people, the request fails with a `400` instead of picking one arbitrarily. To link all of them, up to 50 records per value, use the target record reference format with `if_multiple_matches: "match_all"`.
* **Duplicates are rejected.** If the same record is referenced more than once in one attribute, for example by ID and by a matching value, the request fails with a `400`.
* **Records are not created.** If no record matches, the request fails with a `400` naming the value that could not be found.

To create the missing record instead of failing, use the target record reference format. The example below logs a call and creates the Acme company record if no company has that domain:

<CodeGroup>
  ```json Link the company, creating it if it does not exist theme={"system"}
  {
    "data": {
      "values": {
        "title": "Renewal call",
        "timestamp": "2026-08-14T11:50:00.000Z",
        "direction": "Outbound",
        "linked_records": [
          {
            "target_object": "companies",
            "target_record_reference": {
              "match": {"domains": ["acme.com"]},
              "if_unmatched": "create",
              "create": {"name": "Acme"}
            }
          }
        ],
        "participants": [
          {
            "target_object": "people",
            "email_addresses": [{"email_address": "alice@acme.com"}]
          }
        ]
      }
    }
  }
  ```
</CodeGroup>

See [linking referenced records](/rest-api/guides/linking-referenced-records) for the full behaviour, the limits and the errors. Note that every referenced record created this way counts against your [write rate limit](/rest-api/guides/rate-limiting).

## Querying activity records

Query records of an activity with `POST /v2/activities/{activity}/records/query`. The body supports the same `filter` and `sorts` as [object record queries](/rest-api/guides/filtering-and-sorting), plus `limit` (default 500) and `offset` for [pagination](/rest-api/guides/pagination).

<CodeGroup>
  ```json Recent outbound calls first theme={"system"}
  {
    "filter": {"direction": "Outbound"},
    "sorts": [{"direction": "desc", "attribute": "timestamp"}],
    "limit": 50,
    "offset": 0
  }
  ```
</CodeGroup>

A single record can be fetched with `GET /v2/activities/{activity}/records/{record_id}` (UUID only).

Activity records are also queryable with [SQL](/sql/overview): each activity is exposed as a table under the `activities` schema, named by its `api_slug`, e.g. `activities.phone_calls`. See [Available data](/sql/available-data) for the full picture of what's queryable.

## Updating activity records

Activity records support the same two update methods as object records:

* `PUT /v2/activities/{activity}/records/{record_id}` overwrites: for multiselect attributes, the values you supply replace the existing list of values.
* `PATCH /v2/activities/{activity}/records/{record_id}` appends: for multiselect attributes, the values you supply are added to the existing list of values.

Both accept the same `values` map, including all three record reference formats, as record creation.

## Upserting activity records

`PUT /v2/activities/{activity}/records?matching_attribute={attribute}` creates an activity record, or updates the record that has the same value for the matching attribute. `{attribute}` is the slug or ID of an attribute on the activity, for example an ID from your own system. If more than one record matches the value, the request fails with a `400`. It needs the same scopes as record creation.

## Deleting activity records

`DELETE /v2/activities/{activity}/records/{record_id}` deletes a single activity record. It needs `activity_record:read-write`.

## Webhooks

Activities emit seven [webhook](/rest-api/guides/webhooks) event types.

| Event type | Fired when | `id` contains |
| - | - | - |
| `activity.created` | An activity is created | `workspace_id`, `activity_id` |
| `activity.updated` | An activity is updated, e.g. renamed | `workspace_id`, `activity_id` |
| `activity.deleted` | An activity is deleted | `workspace_id`, `activity_id` |
| `activity-attribute.created` | An attribute is added to an activity | `workspace_id`, `activity_id`, `attribute_id` |
| `activity-attribute.updated` | An attribute of an activity is updated | `workspace_id`, `activity_id`, `attribute_id` |
| `activity-record.created` | An activity record is created | `workspace_id`, `activity_id`, `record_id` |
| `activity-record.deleted` | An activity record is deleted | `workspace_id`, `activity_id`, `record_id` |

Every payload also carries an `actor`. Note that the `id` of an activity event uses `activity_id` where an object event would use `object_id`.

<CodeGroup>
  ```json An activity-record.created event theme={"system"}
  {
    "webhook_id": "d3f1a0e6-6a1a-4bc8-9f3d-0f3f0b2a1c44",
    "events": [
      {
        "event_type": "activity-record.created",
        "id": {
          "workspace_id": "14beef7a-99f7-4534-a87e-70b564330a4c",
          "activity_id": "97052eb9-e65e-443f-a297-f2d9a4a7f795",
          "record_id": "891dcbfc-9141-415d-9b2a-2238a6cc012d"
        },
        "actor": {
          "type": "api-token",
          "id": "bb9e6b41-e4d2-4b6b-9a79-3c7d0e0b8b9d"
        }
      }
    ]
  }
  ```
</CodeGroup>

### Subscribing to one activity

A workspace can have many activities, and `activity-record.created` fires for all of them. If you only care about one, use a [filter](/rest-api/guides/webhooks#filtering) on the subscription:

<CodeGroup>
  ```json Only receive records of one activity theme={"system"}
  {
    "$and": [
      {
        "field": "id.activity_id",
        "operator": "equals",
        "value": "97052eb9-e65e-443f-a297-f2d9a4a7f795"
      }
    ]
  }
  ```
</CodeGroup>

Filters match on the fields of the payload, so they take the activity **ID**, not its `api_slug`. The other fields you can filter on are `id.workspace_id`, `id.record_id`, `id.attribute_id`, `actor.type` and `actor.id`.

This is strongly recommended for activities: it keeps the deliveries you do not need from counting against your endpoint's delivery rate limit.

## Current limitations

* Activity records have no `web_url`.
* There is no `activity-record.updated` webhook event.
* Activities cannot be added to [lists](/docs/objects-and-lists).
* The status endpoints do not accept `activities` as a target. Use select attributes instead.

<Card title="Activities concept guide" icon="book" href="/docs/activities">
  How activities, activity records and schemas fit into Attio's data model.
</Card>


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