---
description: >-
  Read the project audit log through the Verda API, filter and page through
  events, and export the whole log as a JSON file.
revision_date: 30.07.2026
---

# Public API

The Public API exposes two endpoints for the audit log:

* [`GET /v1/audit/log`](https://api.verda.com/v1/docs#tag/journal/GET/v1/audit/log) returns events newest first, page by page.
* [`POST /v1/audit/log/download`](https://api.verda.com/v1/docs#tag/journal/POST/v1/audit/log/download) exports every matching event into one JSON file and returns a short-lived download link.

Both accept the same filters and require the project owner or an `admin` member.

***

## Authenticate

Requests use an access token from the OAuth 2.0 client credentials flow, with the [Cloud API credentials](https://docs.verda.com/welcome-to-verda/api-credentials/) of the project whose log you want to read:

```bash
ACCESS_TOKEN=$(curl -s -X POST https://api.verda.com/v1/oauth2/token \
  -H 'Content-Type: application/json' \
  -d '{"grant_type":"client_credentials","client_id":"<CLIENT_ID>","client_secret":"<CLIENT_SECRET>"}' \
  | jq -r .access_token)
```

The project is taken from the credentials, so no project id is needed in the request.

***

## Read events

```bash
curl -G https://api.verda.com/v1/audit/log \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -d object_type=compute \
  -d action=delete \
  -d page_size=100
```

The response holds the events in `data` and, when more pages remain, a `cursor`:

```json
{
  "data": [
    {
      "specversion": "1.0",
      "id": "log_033FNaNcc0AtphhH71DLBW",
      "source": "https://api.verda.com/project/7c9e6a41-2f8b-4d3e-9a15-0b6c4d2e8f37",
      "subject": "7bdb4161-c7d2-478c-9850-05719adc8381",
      "type": "com.verda.api.cloud.compute.delete.v1",
      "time": "2026-07-28T14:15:52.872Z",
      "data": {
        "contract": "PAY_AS_YOU_GO",
        "hostname": "tiny-tree-unfolds-fin-01",
        "instance_type": "CPU.4V.16G",
        "location_code": "FIN-01",
        "discontinue_reason": "by_user_action",
        "request_ip": "203.0.113.10",
        "request_origin": "console-11.67.0",
        "actor_id": "5bbb59cb-fced-44a4-85c6-5005e7480a8f",
        "compute": {
          "id": "7bdb4161-c7d2-478c-9850-05719adc8381",
          "hostname": "tiny-tree-unfolds-fin-01",
          "instance_type": "CPU.4V.16G",
          "ip": "192.0.2.15",
          "os_volume_id": "2b7d0e2e-fc8d-4e9c-8b89-6117a4d8d941",
          "location_code": "FIN-01",
          "is_cluster": false
        }
      }
    }
  ],
  "cursor": "log_02yTr8Kc11BspqLM40XZQV"
}
```

### Query parameters

* `object_type=compute`: return events for one object type, for example `compute`, `volume`, `ssh_key`, `user`. See [supported events](https://docs.verda.com/resources/audit-logs/supported-events/).
* `action=delete`: return events for one action, for example `create`, `delete`, `login`.
* `start_date=2026-07-01T00:00:00.000Z`: events at or after this timestamp. Cannot be more than 90 days ago. Defaults to 90 days ago.
* `end_date=2026-07-31T00:00:00.000Z`: events at or before this timestamp. Cannot be before `start_date`.
* `page_size=100`: events per page, from 1 to 100. Defaults to 20.
* `cursor=log_02yTr8Kc11BspqLM40XZQV`: continue from the `cursor` returned by the previous response.

***

## Page through the log

This endpoint uses cursor pagination rather than the `page` and `pageSize` parameters used elsewhere in the API. Pass the `cursor` from each response back in the next request. The last page has no `cursor`.

Because the order is fixed (newest first, by event time), events written while you page do not shift the ones you have already read.

=== "Python"

    ```python
    import requests

    URL = "https://api.verda.com/v1/audit/log"
    headers = {"Authorization": f"Bearer {access_token}"}
    params = {"object_type": "compute", "page_size": 100}

    events = []
    while True:
        response = requests.get(URL, headers=headers, params=params, timeout=60)
        response.raise_for_status()
        page = response.json()

        events.extend(page["data"])
        cursor = page.get("cursor")
        if not cursor:
            break
        params["cursor"] = cursor
    ```

=== "cURL"

    ```bash
    # first page
    curl -G https://api.verda.com/v1/audit/log \
      -H "Authorization: Bearer $ACCESS_TOKEN" \
      -d page_size=100

    # next page, using the cursor from the previous response
    curl -G https://api.verda.com/v1/audit/log \
      -H "Authorization: Bearer $ACCESS_TOKEN" \
      -d page_size=100 \
      -d cursor=log_02yTr8Kc11BspqLM40XZQV
    ```

***

## Exporting to a JSON file

`POST /v1/audit/log/download` takes the same filters as the read endpoint, without `page_size` and `cursor`. Verda writes every matching event to a file in object storage and returns a pre-signed link to it:

```bash
curl -X POST https://api.verda.com/v1/audit/log/download \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"object_type":"compute","start_date":"2026-07-01T00:00:00.000Z"}'
```

```json
{
  "url": "https://objects.fin-03.verda.storage/audit-logs/7c9e6a41-2f8b-4d3e-9a15-0b6c4d2e8f37/2026/audit-log-20260730-141552-3f0c9a71-b08f-4d48-9e03-6ab6196c186b.json?X-Amz-Signature=...",
  "expires_at": "2026-07-30T14:30:52.872Z"
}
```

Download it with any HTTP client, no extra credentials needed:

```bash
curl -o audit-log.json "<url from the response>"
```

* The file is a single JSON array of the same event objects the read endpoint returns, newest first.
* The link is valid for 15 minutes, until `expires_at`. Request a new export once it expires.
* Each export writes a new file, so exporting twice with the same filters gives you two links.
* The file is written before the response comes back. For a busy project, filter by date range or object type to keep the export small and the request short.

***

## Errors

| Status | Reason                                                                                          |
| ------ | ----------------------------------------------------------------------------------------------- |
| `400`  | `start_date` is more than 90 days ago, or `end_date` is before `start_date`                      |
| `401`  | The access token is missing or expired                                                            |
| `403`  | The credentials belong to a `developer` member rather than the project owner or an `admin` member |
