# Activity log

The activity log records what happens in your tenant. Sign-ins, document uploads, permission changes and changes to companies each produce an entry. An entry says what happened, who did it, when, and which records it touched.

You can read the activity log with these endpoints:

| Endpoint | What it returns |
|---|---|
| `GET /activities` | Entries from the activity log, newest first |
| `GET /activities/types` | The values the `types` filter accepts |
| `GET /activities/resource-types` | The values the `resourceType` filter accepts |

Only your own tenant's entries are returned.

## Before you start

All three endpoints need the `LIST_ACTIVITIES` permission. Tokens don't have it by default.

1. In Avallone, go to **Settings → Permissions**.
2. Open the **Default API Permissions Group** and add the `LIST_ACTIVITIES` feature.
3. Create a new token, and switch your integration to it. A token keeps the permissions it had when it was created, so older tokens still get `403`. See [Permissions](/authentication#permissions).

## List entries

```http
GET /api/v1/activities
```

Returns the activity log, newest first. Entries recorded at the same instant are ordered by `id`.

**Permission:** `LIST_ACTIVITIES`

### Query parameters

Every parameter is optional. Without filters, you page through the whole log of your tenant.

| Param | Type | Description |
|---|---|---|
| `page` | integer | The page to return. The first page is `0`, which is the default. |
| `limit` | integer | Entries per page, from `1` to `100`. The default is `15`. |
| `types` | string | Only entries of this activity type. Repeat the parameter to ask for several types. See [List activity types](#list-activity-types). |
| `resourceType` | string | Only entries recorded against this kind of record, such as `Company`. See [List resource types](#list-resource-types). |
| `resourceId` | UUID | Only entries recorded against this record |
| `userId` | UUID | Only entries made by this user |
| `from` | timestamp or date | Only entries created at or after this point in time |
| `to` | timestamp or date | Only entries created before this point in time |

How filters combine:

- Repeated `types` are combined with OR. `?types=login&types=switch-tenant` returns sign-ins and tenant switches.
- Different filters are combined with AND. `?types=login&userId=…` returns the sign-ins of one user.
- `resourceType` and `resourceId` are usually sent together. `resourceId` alone matches any record with that ID.
- A `resourceId` or `userId` that matches no record returns an empty list, not `404`.

How the time range works:

- `from` and `to` take an RFC 3339 timestamp, such as `2026-03-01T09:00:00Z`, or a calendar date, such as `2026-03-01`. A date means midnight UTC at the start of that day.
- A timestamp must have seconds, and must end with `Z` for UTC or an offset such as `+01:00`. One with neither is rejected. It is never assumed to be UTC.
- `from` is included and `to` is not. So `from=2026-03-01&to=2026-04-01` covers all of March.
- Back-to-back ranges, such as one day and then the next, never return the same entry twice.

These requests return `400`:

- `page` below `0`, or `limit` outside `1` to `100`;
- a `types` value that isn't an activity type;
- a `resourceType` value that isn't a resource type;
- a `resourceId` or `userId` that isn't a UUID;
- a `from` or `to` that isn't an RFC 3339 timestamp or a `YYYY-MM-DD` date;
- a query parameter this endpoint doesn't know.

### Response

```json
{
  "results": [
    {
      "id": "3f2a1c40-9c1e-4c7f-9c6b-2b0f7a5d1e88",
      "tenantId": "8b1d0c22-5e4a-4f3b-9a77-1c2d3e4f5a6b",
      "type": "company-edited",
      "message": "Jane Doe edited Acme Inc",
      "createdAt": "2026-03-14T09:21:44.512Z",
      "actor": {
        "userId": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
        "name": "Jane Doe",
        "email": "jane@example.com",
        "ipAddress": "203.0.113.42"
      },
      "data": [
        {
          "type": "change",
          "field": "name",
          "before": "Acme",
          "after": "Acme Inc"
        }
      ],
      "resources": [
        { "type": "Company", "id": "5d6e7f80-1a2b-4c3d-9e8f-7a6b5c4d3e2f" }
      ]
    }
  ],
  "metadata": {
    "currentPage": 0,
    "totalPages": 12,
    "limit": 15,
    "nextPageUrl": "/api/v1/activities?page=1&limit=15"
  }
}
```

Each entry in `results` has these fields:

| Field | Description |
|---|---|
| `id` | The ID of the entry |
| `tenantId` | Your tenant's ID |
| `type` | What happened, as an activity type such as `login` or `company-edited` |
| `message` | What happened, as a sentence for people to read. Show it, but don't parse it: its wording can change. |
| `createdAt` | When it happened |
| `actor` | Who did it. See below. |
| `data` | What changed, and other details of the event. See below. |
| `resources` | The records the entry is about. See below. |

An entry never changes after it is recorded.

**`actor`** describes who did it:

| Field | Description |
|---|---|
| `userId` | The ID of the user or API user. `null` when Avallone did it, for example in a scheduled job. This is the ID the `userId` filter matches. |
| `name` | The user's name when the entry was recorded. Can be `null`. |
| `email` | The user's email address when the entry was recorded. Can be `null`. |
| `ipAddress` | The IP address the request came from. `null` when there is none, for example because the entry was recorded by a background job. |

**`data`** is a list. It is empty when the event records no details. Each item has a `type` that says which kind it is:

| `type` | Fields | Meaning |
|---|---|---|
| `change` | `field`, `before`, `after` | One field that changed. `before` or `after` is `null` when the field had no value. What they contain depends on the field. |
| `metadata` | `metadata` | Other details of the event, as an object |

We may add kinds of item. Skip an item whose `type` you don't know.

**`resources`** lists the records the entry is about, each with its `type` and `id`. An entry can be about several records. For example, an upload is about the company and the document. A filter on one of them still returns all of them.

### Pagination

`metadata` describes the page:

| Field | Description |
|---|---|
| `currentPage` | The page you asked for |
| `totalPages` | How many pages the list has, with your filters |
| `limit` | Entries per page |
| `nextPageUrl` | The path of the next page, with your filters. `null` on the last page. Add it to `https://api.avallone.io`. |

A page after the last one returns an empty `results`, as does a filter that matches nothing.

New entries are added at the start of the list, so they push older entries onto later pages while you page through. You can then see an entry twice. To avoid this, set `to` to the time you started, and send it on every page.

### Examples

Everything recorded against one company in March:

```http
GET /api/v1/activities?resourceType=Company&resourceId=5d6e7f80-1a2b-4c3d-9e8f-7a6b5c4d3e2f&from=2026-03-01&to=2026-04-01
```

All sign-ins and tenant switches of one user, 50 at a time:

```http
GET /api/v1/activities?types=login&types=switch-tenant&userId=a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d&limit=50
```

Companies that were deleted since your last run:

```http
GET /api/v1/activities?types=company-deleted&from=2026-03-14T09:00:00Z
```

## List activity types

```http
GET /api/v1/activities/types
```

Returns every activity type the log can record, in alphabetical order. These are the values the `types` filter accepts. Use this endpoint to build a list of types, instead of hard-coding one.

The list includes types that have never been recorded in your tenant. We add types over time.

**Permission:** `LIST_ACTIVITIES`

**Response:** the types under `results`. The list isn't paginated. This shortened sample shows a few of them:

```json
{
  "results": [
    "API-access-logs-requested",
    "API-token-create",
    "company-created",
    "company-deleted",
    "company-edited",
    "document-uploaded",
    "login",
    "switch-tenant"
  ]
}
```

## List resource types

```http
GET /api/v1/activities/resource-types
```

Returns the kinds of record the log has recorded entries against, in alphabetical order. These are the values the `resourceType` filter accepts.

This list covers every tenant in Avallone, so it can include types that never occur in yours. A type recorded for the first time can take up to an hour to appear.

**Permission:** `LIST_ACTIVITIES`

**Response:** the resource types under `results`. The list isn't paginated. This shortened sample shows a few of them:

```json
{
  "results": [
    "ApiToken",
    "ApiUser",
    "Company",
    "CompanyDocument",
    "Counterparty",
    "RequestEntity",
    "UserTenantRole"
  ]
}
```

## Value formats

Activity types and resource types come from Avallone's internal activity log, and keep its format. They don't follow the lowercase snake_case of other values in the API.

- Activity types are kebab-case. Some start with `API-`, such as `API-token-create`.
- Resource types are PascalCase, such as `CompanyDocument`.

Compare them exactly as they are listed.

## Errors

Errors use the [error format](/api-design-guidelines#errors) of the API.

| Status | `code` | When |
|---|---|---|
| `400` | `validation_failed` | A query parameter is invalid. `errors` names each one. See [Query parameters](#query-parameters). |
| `401` | `unauthenticated` | The token is missing, malformed, expired or revoked. |
| `403` | `permission_denied` | The token doesn't have `LIST_ACTIVITIES`. |
