# Access logs

> [!NOTE]
> Access logs are off by default. Avallone support must turn them on for your tenant before you can list or download them. See [Before you start](#before-you-start).

Access logs record the HTTP requests your tenant's users and integrations made to Avallone, and how Avallone answered them. They also hold the messages Avallone logged while it handled those requests.

The logs are stored as one file per UTC day. You download one day at a time.

Access logs record traffic. To see what that traffic changed, such as which company was edited, use the [activity log](/activities).

| Endpoint | What it returns |
|---|---|
| `GET /access-logs` | The days that have a file, newest first |
| `GET /access-logs/{date}` | One day and the size of its file |
| `GET /access-logs/{date}/content` | The file of one day |

A day is identified by its date, as `YYYY-MM-DD` in UTC. It has no other ID.

## Before you start

You need both of these:

1. **Access logs turned on for your tenant.** Contact Avallone support to turn them on. You can't turn them on yourself. Until then, every request returns `403`.
2. **The `LIST_ACCESS_LOGS` permission on your token.** 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_ACCESS_LOGS` 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).

Only API tokens can read access logs.

## List days

```http
GET /api/v1/access-logs
```

Returns the days that have a file, newest first. A day without any requests has no file, so it isn't listed.

**Permission:** `LIST_ACCESS_LOGS`

### Query parameters

Every parameter is optional.

| Param | Type | Description |
|---|---|---|
| `page` | integer | The page to return. The first page is `0`, which is the default. |
| `limit` | integer | Days per page, from `1` to `100`. The default is `15`. |
| `from` | date | Only days on or after this date |
| `to` | date | Only days before this date. So `from=2026-09-01&to=2026-10-01` lists the days in September. |

These requests return `400`:

- `page` below `0`, or `limit` outside `1` to `100`;
- a `from` or `to` that isn't a `YYYY-MM-DD` date;
- a query parameter this endpoint doesn't know.

### Response

```json
{
  "results": [
    {
      "date": "2026-09-25",
      "url": "/api/v1/access-logs/2026-09-25",
      "contentUrl": "/api/v1/access-logs/2026-09-25/content",
      "size": 1834211
    }
  ],
  "metadata": {
    "currentPage": 0,
    "totalPages": 3,
    "limit": 15,
    "nextPageUrl": "/api/v1/access-logs?page=1&limit=15"
  }
}
```

Each day in `results` has these fields:

| Field | Description |
|---|---|
| `date` | The UTC day, as `YYYY-MM-DD`. This is the day's identifier. |
| `url` | The path of the day |
| `contentUrl` | The path that downloads the day's file. See [Download one day](#download-one-day). |
| `size` | The size of the file in bytes, compressed |

`metadata` works as in [Pagination](/api-design-guidelines#pagination). `nextPageUrl` is `null` on the last page.

## Get one day

```http
GET /api/v1/access-logs/{date}
```

Returns one day, with the same fields as in the list. Use it to check whether a day has a file, and how large it is, before you download it.

**Permission:** `LIST_ACCESS_LOGS`

`date` is the UTC day, as `YYYY-MM-DD`. A day without a file returns `404`. See [When a day has no file](#when-a-day-has-no-file).

## Download one day

```http
GET /api/v1/access-logs/{date}/content
```

Returns the access log file for one UTC day. Only your own tenant's logs are returned. Each day in the list gives this path as `contentUrl`.

**Permission:** `LIST_ACCESS_LOGS`

### Path parameters

| Param | Type | Description |
|---|---|---|
| `date` | date | The UTC day to download, as `YYYY-MM-DD`. It must be today or earlier. |

### Response

The response body is the file itself, as it was stored. It isn't unzipped or filtered.

| Header | Value |
|---|---|
| `Content-Type` | `application/gzip` |
| `Content-Disposition` | `attachment; filename="access-logs-2026-09-25.jsonl.gz"` |
| `Content-Length` | The size of the file in bytes |

The file is compressed with gzip, but the response doesn't have a `Content-Encoding` header. So your HTTP client saves the compressed file as it is, and doesn't unzip it. Unzip it yourself before you read it.

The download isn't compressed a second time, even when you send `Accept-Encoding: gzip`.

The whole file is always returned. `Range` requests aren't supported.

### What the file contains

The unzipped file holds one JSON object per line ([JSON Lines](https://jsonlines.org/)).

- Most lines describe a request. They have fields such as `_time`, `req.method`, `req.url`, `res.statusCode`, `responseTime`, `user_id` and `tenant_id`.
- Other lines are messages Avallone logged while it handled a request.

The file is a raw export, so it doesn't follow the [field formats](/api-design-guidelines#field-names-and-formats) of the API:

- Field names can contain dots, such as `req.method`. These are flat names, not nested objects.
- Numbers can be stored as strings.
- The set of fields depends on what the export recorded, and can change without notice. Read the fields you need and ignore the rest.

### When a day has no file

You get `404` when there is no file for the day. Either nobody used Avallone that day, or the day hasn't been exported yet. The most recent requests can be missing until the next export runs.

### Example

```bash
curl -H "Authorization: Bearer $TOKEN" \
  -o access-logs-2026-09-25.jsonl.gz \
  "https://api.avallone.io/api/v1/access-logs/2026-09-25/content"

gunzip -c access-logs-2026-09-25.jsonl.gz | head -n 3
```

## Errors

Errors use the [error format](/api-design-guidelines#errors) of the API. These apply to all three endpoints:

| Status | `code` | When |
|---|---|---|
| `400` | `validation_failed` | A query parameter is invalid, or `date` in the path isn't `YYYY-MM-DD`, isn't a real day, or is in the future. |
| `401` | `unauthenticated` | The token is missing, malformed, expired or revoked. |
| `403` | `permission_denied` | The token doesn't have `LIST_ACCESS_LOGS`, or access logs aren't turned on for your tenant. |
| `404` | `not_found` | There is no file for that day. See [When a day has no file](#when-a-day-has-no-file). |

## Downloads are recorded in the activity log

Every download request that passes the token, permission and tenant checks is recorded in your [activity log](/activities). That includes downloads answered with `400` or `404`.

These aren't recorded:

- requests refused with `401` or `403`;
- listing days, and getting one day.

Each entry has:

- `type` set to `API-access-logs-requested`;
- the API user in `resources`, with the type `ApiUser`;
- a `metadata` item in `data` with the details below.

| Field | Description |
|---|---|
| `outcome` | How the request ended: `downloaded`, `not_found` or `invalid_date` |
| `date` | The `date` you sent, cut to 32 characters |
| `tokenId` | The ID of the token that made the request |

To list them:

```http
GET /api/v1/activities?types=API-access-logs-requested
```
