# Query events associated with your organization

> GET /v1/audit-logs · Core resources API

Source: https://developer.kustomer.com/api/core-resources/query-events-associated-with-your-organization

```http
GET https://{orgname}.api.kustomerapp.com/v1/audit-logs
Authorization: Bearer <API key>
```

## Authorization

Requires any one of these roles: `org.permission.audit_logs.read`; or legacy `org.admin.audit_logs.read`. The API key or user must hold at least one of them. Legacy roles predate permission sets and still work.

--------

## About Audit logs

The audit log is a read-only history of what happened in your Kustomer org, such as who changed a conversation, who signed in, and what a routing or business rule did. Each entry records the event, the object it touched, who or what did it, and the fields that changed.

Kustomer writes an audit log entry whenever something notable happens to the data in your org: a conversation or customer is updated or deleted, a user signs in or fails to, a business rule is created, a work item is routed, a message is created. Admins see these entries in the audit log screen of the Kustomer admin settings, and the API exposes the same history so you can export it to a SIEM, investigate an incident, or answer "who changed this, and when?". Entries are created only by Kustomer. You can't write, edit or delete them, and each one expires after a retention period, shown in `expiresAt`.

#### What an entry contains

Each entry has type `audit_log` and these key attributes:

| Field | Meaning |
| --- | --- |
| `eventName` | The full event, such as `kustomer.conversation.update` or `kustomer.user-auth.fail`. |
| `eventVerb` | The action alone, such as `create`, `update` or `delete`. |
| `objectType`, `objectId` | The object the event is about. |
| `userId`, `userType` | Who did it. `userType` is `system` and `userId` is `null` when Kustomer itself made the change, for example during routing. |
| `client` | The part of Kustomer the change came from, such as `routing`. |
| `ip` | The caller's IP address, when known. |
| `changes` | For updates, a diff split into `attributes` and `relationships`. Each changed field has `op`, `before` and `after`. |
| `publishedAt`, `createdAt` | When the event happened and when it was recorded. |

#### Filtering

Narrow results with `filter[objectType]` (one of the types in the enum, such as `conversation`, `customer`, `user`, `business_rule`, `work_item` or `work_session`), then optionally `filter[objectId]` for one object. `filter[userId]` returns events by or about one user. `filter[include]` adds more object types to the results with OR logic, and `filter[start]` and `filter[end]` limit the date range. How far back you can search depends on your Kustomer plan.

#### Which endpoint to use

| To | Call |
| --- | --- |
| Search the org's audit history, by object, user or date | [`GET /v1/audit-logs`](https://developer.kustomer.com/api/core-resources/query-events-associated-with-your-organization) |

Results come newest first and use cursor pagination rather than page numbers. `count` sets the page size (default 100, maximum 1000), and you move through pages with the `before` and `after` cursors, which are audit log IDs. The `links.next` and `links.prev` URLs in each response already contain the right cursor, so follow them instead of building cursors yourself. Reading the audit log requires the audit log read permission, which is usually limited to admins.

Related: [Pagination](https://developer.kustomer.com/docs/concepts/pagination), [Users](https://developer.kustomer.com/api/access-management#users), [Work sessions](https://developer.kustomer.com/api/queues-and-routing#work-sessions)

### Recipes

#### 1. See who changed a conversation, and when

Reconstruct a conversation's history of assignments, status changes and edits, for example to answer a customer complaint or review an escalation.

**Step 1.** [`GET /v1/audit-logs`](https://developer.kustomer.com/api/core-resources/query-events-associated-with-your-organization.md). Filter on the conversation with `filter[objectType]` and `filter[objectId]`; `filter[objectId]` doesn't work on its own. Entries come newest first. `publishedAt` is when the change happened, and `changes` holds `before` and `after` for each field Kustomer tracks. Pass `--globoff` so curl sends the square brackets as they are.

```bash title="cURL"
curl --globoff "https://api.kustomerapp.com/v1/audit-logs?filter[objectType]=conversation&filter[objectId]=$CONVERSATION_ID&count=1000" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY"
```

```bash title="kusty"
kusty api GET /v1/audit-logs --query 'filter[objectType]=conversation' \
  --query "filter[objectId]=$CONVERSATION_ID" --query count=1000 > audit-logs.json
jq -r '.data[].attributes | [.publishedAt, .eventVerb, .userType, (.userId // "-"),
  ((.changes.attributes // {}) + (.changes.relationships // {}) | keys | join(","))] | @tsv' audit-logs.json
```

**Step 2.** [`GET /v1/users/{ids}`](https://developer.kustomer.com/api/access-management/get-multiple-users-by-ids.md). Turn the `userId` values into names in one request. Entries with `userType` `system` have no user, because Kustomer made the change itself, for example when routing assigned the conversation.

```bash title="cURL"
curl "https://api.kustomerapp.com/v1/users/$USER_IDS" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY"
```

```bash title="kusty"
USER_IDS=$(jq -r '.data[].attributes.userId // empty' audit-logs.json | sort -u | paste -sd, -)
kusty api GET /v1/users/$USER_IDS \
  | jq -r '.data | if type == "array" then .[] else . end | [.id, .attributes.displayName] | @tsv'
```

> For everything about one customer, use `filter[objectType]=customer` with the customer's ID and add `filter[include]=conversation` to include changes to their conversations. Only the fields Kustomer tracks appear in `changes`, so it isn't a complete record of every edit.

#### 2. Review a user's sign-in history

List a user's successful and failed sign-ins with the IP address of each, for example when investigating a suspicious account.

**Step 1.** [`GET /v1/users`](https://developer.kustomer.com/api/access-management/get-users.md). Find the user's ID from their email address.

```bash title="cURL"
curl "https://api.kustomerapp.com/v1/users?email=$EMAIL" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY"
```

```bash title="kusty"
USER_ID=$(kusty api GET /v1/users --query email=$EMAIL | jq -r '.data[0].id')
```

**Step 2.** [`GET /v1/audit-logs`](https://developer.kustomer.com/api/core-resources/query-events-associated-with-your-organization.md). Filter on the user with `filter[objectType]=user` and their ID, then keep the sign-in events, whose names start with `kustomer.user-auth.`. `eventVerb` says what happened, such as `success` or `fail`, and `ip` is where the attempt came from. The same filter also returns changes to the user's profile, which the `select` drops.

```bash title="cURL"
curl --globoff "https://api.kustomerapp.com/v1/audit-logs?filter[objectType]=user&filter[objectId]=$USER_ID&count=1000" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY"
```

```bash title="kusty"
kusty api GET /v1/audit-logs --query 'filter[objectType]=user' \
  --query "filter[objectId]=$USER_ID" --query count=1000 \
  | jq -r '.data[].attributes | select(.eventName | startswith("kustomer.user-auth."))
    | [.publishedAt, .eventVerb, (.ip // "-")] | @tsv'
```

> For older entries, repeat the request with the `before` cursor from `links.next`, as in the export recipe below. Add `filter[start]` and `filter[end]` to look at one period. How far back entries go depends on your Kustomer plan.

#### 3. Export audit logs to a SIEM on a schedule

Copy new audit log entries into your security tooling every few minutes, without gaps or duplicates.

**Step 1.** [`GET /v1/audit-logs`](https://developer.kustomer.com/api/core-resources/query-events-associated-with-your-organization.md). Ask for one time window at a time with `filter[start]` and `filter[end]` (ISO 8601 timestamps, always sent together) and the largest page, `count=1000`. Start each run's window a few minutes before the previous run's end, so entries recorded late aren't missed.

```bash title="cURL"
curl --globoff "https://api.kustomerapp.com/v1/audit-logs?filter[start]=$START&filter[end]=$END&count=1000" \
  --header "Authorization: Bearer $KUSTOMER_API_KEY"
```

```bash title="kusty"
kusty api GET /v1/audit-logs --query "filter[start]=$START" --query "filter[end]=$END" \
  --query count=1000 > page.json
```

**Step 2.** [`GET /v1/audit-logs`](https://developer.kustomer.com/api/core-resources/query-events-associated-with-your-organization.md). Page backwards by repeating the request with the `before` cursor from `links.next`. Stop when `links.next` has no `before`: on the last page it points back to the first page. A page can hold fewer than `count` entries even when more remain, so don't stop on a short page. Don't use kusty's `--all` here, because it stops with an error when `links.next` goes back to the start.

```bash title="cURL"
BEFORE=""
while :; do
  curl --globoff --silent "https://api.kustomerapp.com/v1/audit-logs?filter[start]=$START&filter[end]=$END&count=1000${BEFORE:+&before=$BEFORE}" \
    --header "Authorization: Bearer $KUSTOMER_API_KEY" > page.json
  jq -c '.data[]' page.json >> export.jsonl
  BEFORE=$(jq -r '.links.next // "" | capture("before=(?<id>[^&]+)").id // empty' page.json)
  [ -n "$BEFORE" ] || break
done
```

```bash title="kusty"
BEFORE=""
while :; do
  kusty api GET /v1/audit-logs --query "filter[start]=$START" --query "filter[end]=$END" \
    --query count=1000 ${BEFORE:+--query before=$BEFORE} > page.json
  jq -c '.data[]' page.json >> export.jsonl
  BEFORE=$(jq -r '.links.next // "" | capture("before=(?<id>[^&]+)").id // empty' page.json)
  [ -n "$BEFORE" ] || break
done
```

**Step 3.** Because the windows overlap, some entries come back in two runs. Drop duplicates by `id`, and skip any ID you forwarded in the previous run, before sending the rest on.

```bash title="kusty"
jq -s 'unique_by(.id) | .[]' -c export.jsonl > new-entries.jsonl
```

> Keep your own copy if you need history longer than your plan's retention: each entry is deleted at its `expiresAt`. Reading audit logs needs the audit log read permission, so give the exporter's API key `org.permission.audit_logs.read`.

### Example object

A real record from [`GET /v1/audit-logs`](https://developer.kustomer.com/api/core-resources/query-events-associated-with-your-organization.md):

```json
{
  "type": "audit_log",
  "id": "62799c44a96e7f160834199c",
  "attributes": {
    "eventName": "kustomer.conversation.update",
    "eventVerb": "update",
    "org": "59775dcda751900011ff4687",
    "userId": null,
    "userType": "system",
    "objectId": "5faaf4a6ea0cb48cd0801bbf",
    "objectType": "conversation",
    "client": "routing",
    "ip": null,
    "changes": {
      "attributes": {
        "assignedUsers": {
          "op": "replace",
          "before": [],
          "after": [
            "5cb5e5a780b7e30014e4b6c3"
          ]
        }
      },
      "relationships": {
        "company": {
          "op": "replace",
          "before": "6195640664b2fbb9eddc1e9b",
          "after": "627aa25d0a0e2a1ff95268b0"
        }
      }
    },
    "createdAt": "2016-12-09T22:57:01.074Z",
    "publishedAt": "2016-12-09T22:57:08.387Z",
    "expiresAt": "2017-12-07T22:57:10.074Z"
  },
  "relationships": {
    "customer": {
      "links": {
        "self": "/v1/customers/5f8efdd5d3de932f344ca367"
      },
      "data": {
        "type": "customer",
        "id": "5f8efdd5d3de932f344ca367"
      }
    },
    "user": {
      "links": {
        "self": "/v1/users/null"
      },
      "data": {
        "type": "user",
        "id": null
      }
    }
  }
}
```

Fields:

- `attributes.eventName` (string): The full event name, such as `kustomer.conversation.update` or `kustomer.user-auth.fail`.
- `attributes.eventVerb` (string): The last part of `eventName`, such as `create`, `update`, `delete`, `success` or `fail`.
- `attributes.userId` (string): The user who made the change, or `null` when Kustomer made it. For sign-in events, the user signing in.
- `attributes.userType` (string): Who made the change. `system` means Kustomer itself, as with this routing change, and then `userId` is `null`.
- `attributes.objectId` (string): The ID of that object. Filter on it with `filter[objectId]`, which also needs `filter[objectType]`.
- `attributes.objectType` (string): The type of object the event is about. It takes the same values as `filter[objectType]`.
- `attributes.client` (string): The part of Kustomer the change came from, when known, such as `routing`.
- `attributes.ip` (string): The IP address of the request behind the event, when there was one.
- `attributes.changes` (object): For updates, what changed, split into `attributes` and `relationships`. Only the fields Kustomer tracks for that object type appear, so it isn't a full diff.
- `attributes.changes.attributes.assignedUsers` (object): One changed field. `op` is the kind of change, such as `replace`, and `before` and `after` hold the old and new values.
- `attributes.changes.relationships.company` (object): A changed relationship. `before` and `after` are the related objects' IDs.
- `attributes.createdAt` (string): When the entry was recorded, usually just after the event.
- `attributes.publishedAt` (string): When the event happened.
- `attributes.expiresAt` (string): When the entry will be deleted, set from the retention period when it's recorded.
- `relationships.customer` (object): The customer the event involves, when there is one, such as the customer of an updated conversation.
- `relationships.user` (object): The user who made the change. Its `id` is `null` when Kustomer made it.

### Relationships

- Points to **Customers** via `relationships.customer`: The customer an audit log entry involves, such as the customer whose conversation changed. For a customer's own history, filter with `filter[objectType]=customer` and `filter[objectId]`, and add `filter[include]=conversation` to include their conversations.
- Points to **Users** via `relationships.user`: The user whose action an audit log entry records. `filter[userId]` returns the entries by or about one user.

### Permissions

Fewest roles for a read-only integration: `org.permission.audit_logs.read`. For every endpoint: `org.permission.audit_logs.read`.

## Query parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `count` | string | no | Limits the number audit-log documents to be returned. Default 100, maximum 1000. |
| `after` | string | no | Paginates by the ID of the audit-log which marks the end of the pagination range. Requires "before". |
| `before` | string | no | Paginates by the ID of the audit-log which marks the beginning of the pagination range. Requires "after". |
| `filter[objectType]` | string | no | Filters by the type of database object the event relates to. |
| `filter[objectId]` | string | no | Filters by the id of database object the event relates to. Requires `filter[objectType]`. |
| `filter[userId]` | string | no | Filters by the ID of the user the event relates to. |
| `filter[include]` | string | no | Adds additional OR query logic to include results for additional objectTypes. For available objectTypes to use in this query param, see the enum in the `filter[objectType]` field. |
| `filter[start]` | string | no | Filter by the date range start. |
| `filter[end]` | string | no | Filter by the date range end. |

## Responses

### 200

OK

- `data` (object, required)
  - `type` (string, required)
  - `id` (string, required)
  - `attributes` (object, required)
    - `eventName` (string, required)
    - `eventVerb` (string, required)
    - `org` (string, required)
    - `userId` (string)
    - `userType` (string)
    - `objectId` (string)
    - `objectType` (string)
    - `client` (string)
    - `ip` (string)
    - `changes` (object)
    - `createdAt` (string)
    - `publishedAt` (string)
    - `expiresAt` (string)
  - `relationships` (object, required)
    - `user` (object, required)
  - `links` (object, required)
    - `self` (string, required)
    - `first` (string, required)
    - `previous` (string, required)
    - `next` (string, required)

```json
{
  "meta": {
    "count": 1,
    "total": 3000,
    "cursorCount": 3000
  },
  "links": {
    "self": "/v1/audit-logs?count=1",
    "first": "/v1/audit-logs?count=1",
    "prev": "/v1/audit-logs?count=1&after=62799c44a96e7f160834199c",
    "next": "/v1/audit-logs?count=1&before=62799c44a96e7f160834199c"
  },
  "data": [
    {
      "type": "audit_log",
      "id": "62799c44a96e7f160834199c",
      "attributes": {
        "eventName": "kustomer.conversation.update",
        "eventVerb": "update",
        "org": "59775dcda751900011ff4687",
        "userId": null,
        "userType": "system",
        "objectId": "5faaf4a6ea0cb48cd0801bbf",
        "objectType": "conversation",
        "client": "routing",
        "ip": null,
        "changes": {
          "attributes": {
            "assignedUsers": {
              "op": "replace",
              "before": [],
              "after": [
                "5cb5e5a780b7e30014e4b6c3"
              ]
            }
          },
          "relationships": {
            "company": {
              "op": "replace",
              "before": "6195640664b2fbb9eddc1e9b",
              "after": "627aa25d0a0e2a1ff95268b0"
            }
          }
        },
        "createdAt": "2016-12-09T22:57:01.074Z",
        "publishedAt": "2016-12-09T22:57:08.387Z",
        "expiresAt": "2017-12-07T22:57:10.074Z"
      },
      "relationships": {
        "customer": {
          "links": {
            "self": "/v1/customers/5f8efdd5d3de932f344ca367"
          },
          "data": {
            "type": "customer",
            "id": "5f8efdd5d3de932f344ca367"
          }
        },
        "user": {
          "links": {
            "self": "/v1/users/null"
          },
          "data": {
            "type": "user",
            "id": null
          }
        }
      }
    }
  ]
}
```

### 400

Bad Request

### 401

Unauthorized

### 403

Forbidden

## Example request

```bash
curl --request GET \
  --url 'https://api.kustomerapp.com/v1/audit-logs' \
  --header "Authorization: Bearer $KUSTOMER_API_KEY"
```
