> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.trebellar.app/llms.txt.
> For full documentation content, see https://docs.trebellar.app/llms-full.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.trebellar.app/_mcp/server.

# Audit log delivery

Trebellar emits an **audit log** for every security-relevant action that happens on your
tenant — authentication events, user-management changes, and other governance-critical
operations. These logs are delivered to cloud object storage once per day so you can
retain them for as long as your compliance program requires, stream them into your SIEM,
or query them ad-hoc with your warehouse of choice.

This page explains:

* How the delivery pipeline works and what the files look like
* How to configure delivery (bring-your-own-bucket, or hosted on Trebellar)
* The full schema of a single audit record
* The catalog of event types Trebellar currently emits

Audit log delivery is an organization-level feature and must be enabled by an
administrator from the Trebellar admin panel at
[my.trebellar.app](https://my.trebellar.app).

## How delivery works

Audit events are written as structured records to Trebellar's log pipeline in real time,
then batched, serialized as **newline-delimited JSON (NDJSON)**, and delivered to the
destination of your choice **once every 24 hours**.

Each daily file contains every audit record produced for your organization during the
prior UTC day. Files are written under a deterministic key prefix so they are easy to
ingest incrementally:

```text
audit-logs/
  orgId=<your-org-id>/
    year=2026/
      month=04/
        day=19/
          trebellar-audit-2026-04-19.ndjson
```

Key properties of the delivery:

| Property         | Value                                                                                                                                              |
| ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Format**       | NDJSON (one JSON object per line, UTF-8)                                                                                                           |
| **Cadence**      | Once per UTC day, typically within a few hours of `00:00 UTC`                                                                                      |
| **Partitioning** | By `orgId` and calendar day                                                                                                                        |
| **Ordering**     | Records are ordered by `audit_event.eventTime` within a file, but downstream consumers should not rely on strict ordering                          |
| **Completeness** | Each file contains every event emitted for the partition window. Late-arriving events (\< 5 min skew) are rolled into the file before it is closed |
| **Retention**    | Trebellar retains the originating logs for 30 days. Long-term retention is your responsibility once the file is delivered                          |

NDJSON is easy to read line-by-line without loading the full file into memory, and is
natively supported by BigQuery, Snowflake, Redshift Spectrum, Athena, DuckDB, `jq`,
and most SIEMs.

## Configuration

There are two delivery modes. Both are configured from the **Admin panel →
Governance → Audit logs** section of the Trebellar platform.

### Option 1 — Bring your own bucket

Use this mode if you want the logs to land directly in cloud storage that you own. This
is the recommended mode for regulated workloads because the data never leaves your
tenancy after the handoff.

You provide:

| Field                   | Description                                                                                                |
| ----------------------- | ---------------------------------------------------------------------------------------------------------- |
| **Bucket URL**          | The fully qualified bucket URI — e.g. `s3://acme-trebellar-audit-logs` or `gs://acme-trebellar-audit-logs` |
| **Region**              | The bucket's region (required for S3)                                                                      |
| **Client ID**           | Access key ID / service account client identifier that Trebellar will use to authenticate when writing     |
| **Client secret**       | The matching secret. Trebellar encrypts this at rest and never returns it in the admin UI after saving     |
| **Prefix** *(optional)* | A key prefix under which files will be written — defaults to `audit-logs/`                                 |

Steps:

1. Create a dedicated bucket in your cloud provider (S3 or GCS).
2. Provision an IAM principal (IAM user for S3, service account for GCS) that has **only**
   `PutObject` / `storage.objects.create` on the target prefix. Do not grant list,
   read, or delete permissions — Trebellar does not need them.
3. Generate a long-lived access key and secret for that principal.
4. In **Admin panel → Governance → Audit logs**, choose **Bring your own bucket** and paste
   the values above.
5. Click **Test connection**. Trebellar will write a small `.trebellar-healthcheck` object
   and delete it. If the test succeeds, delivery starts on the next daily cycle.

Rotate the client secret periodically. Trebellar surfaces the last-used timestamp
for each credential so you can verify the rotation took effect before revoking the
old key.

#### Locking the bucket to Trebellar's IP ranges

If your security policy requires bucket access to be pinned to a known set of source
IPs, Trebellar publishes a stable list of **egress addresses** used by the audit-log
delivery workers. You can attach these to your bucket policy (`aws:SourceIp` on S3 or
a VPC Service Controls perimeter on GCS) so that writes are only accepted from
Trebellar's infrastructure.

To avoid drift between these docs and the live set, we don't publish the ranges
inline. To request the current list:

1. Contact your **Customer Success** representative, or email
   [support@trebellar.com](mailto:support@trebellar.com).
2. Specify the cloud provider (AWS or GCP) and the region your bucket lives in —
   we'll send back the narrowest range that covers delivery to that region.
3. Trebellar announces changes to the egress set at least **30 days in advance**
   in the [changelog](/changelog). Subscribe to the changelog to be notified
   before you need to update your bucket policy.

Egress pinning is optional. Delivery works without an IP allowlist — the bucket
credentials are scoped narrowly enough that IP pinning is defense-in-depth
rather than a requirement.

### Option 2 — Use the Trebellar bucket

If you don't need the logs to land in your own cloud, Trebellar can host them in a
tenant-isolated bucket managed on your behalf. You'll be issued a short-lived,
read-only pre-signed URL (refreshed every 7 days) that you can use to pull the files
into your SIEM or warehouse.

Steps:

1. In **Admin panel → Governance → Audit logs**, choose **Use Trebellar bucket**.
2. Pick a retention window (30, 90, 180, or 365 days).
3. Save. The admin panel will display a **Download URL** and a **Manifest URL** that
   you can poll for the list of available files.

In hosted mode, files are deleted at the end of the retention window. Export them to
your own long-term storage if you need them longer.

## Record schema

Every line of the NDJSON file is a single JSON object with the fields below. The shape is
stable — Trebellar will only add new optional fields under `audit_event.*`, and each
change is announced in the [changelog](/changelog) with at least 30 days notice.

### Top-level fields

| Field                       | Type    | Description                                                                               |
| --------------------------- | ------- | ----------------------------------------------------------------------------------------- |
| `severity`                  | string  | `NOTICE` for `SUCCESS`, `WARNING` for `DENIED`, `ERROR` for `FAILURE`                     |
| `message`                   | string  | Human-readable summary, e.g. `audit.authentication.login.success`                         |
| `event_type`                | string  | Always the literal `"audit"` — useful for routing when multiplexed with other log streams |
| `service_name`              | string  | The Trebellar service that produced the event — see the [event catalog](#event-catalog)   |
| `action_name`               | string  | The specific action within that service                                                   |
| `outcome`                   | string  | One of `SUCCESS`, `FAILURE`, `DENIED`                                                     |
| `org_id`                    | string? | The organization the event is scoped to. Absent on application-level events               |
| `request_id`                | string  | Stable unique ID for the originating HTTP request; matches `audit_event.requestId`        |
| `httpRequest.requestMethod` | string  | `GET`, `POST`, `PUT`, `PATCH`, `DELETE`                                                   |
| `httpRequest.requestUrl`    | string  | The route path (query string stripped)                                                    |
| `httpRequest.remoteIp`      | string? | First value of `X-Forwarded-For`, falling back to the socket IP                           |
| `httpRequest.userAgent`     | string? | The `User-Agent` header from the request                                                  |
| `httpRequest.status`        | number? | HTTP status returned, when available                                                      |

### `audit_event` fields

The `audit_event` object is the authoritative payload for downstream consumers. The
top-level fields above are derived from it and kept for ergonomic querying.

| Field                     | Type              | Description                                                                                   |
| ------------------------- | ----------------- | --------------------------------------------------------------------------------------------- |
| `version`                 | string            | Schema version. Currently `"1.0"`                                                             |
| `auditLevel`              | string            | `ORG_LEVEL` when scoped to an organization, `APPLICATION_LEVEL` otherwise                     |
| `deployEnvironment`       | string            | `production`, `staging`, `development`, etc.                                                  |
| `eventTime`               | string            | ISO-8601 UTC timestamp, millisecond precision                                                 |
| `orgId`                   | string?           | Redundant with top-level `org_id`                                                             |
| `sourceIPAddress`         | string?           | See `httpRequest.remoteIp`                                                                    |
| `userAgent`               | string?           | See `httpRequest.userAgent`                                                                   |
| `sessionId`               | string?           | Trebellar session identifier, when the caller was session-authenticated                       |
| `requestId`               | string            | See top-level `request_id`                                                                    |
| `request.method`          | string            | HTTP method                                                                                   |
| `request.route`           | string            | Route path                                                                                    |
| `userIdentity.type`       | string            | One of `user`, `api_token`, `anonymous`, `system`                                             |
| `userIdentity.id`         | number \| string? | The actor's user ID                                                                           |
| `userIdentity.email`      | string?           | The actor's email                                                                             |
| `userIdentity.name`       | string?           | The actor's display name                                                                      |
| `userIdentity.orgId`      | string?           | The actor's organization                                                                      |
| `userIdentity.role`       | string \| number? | The actor's Trebellar role                                                                    |
| `serviceName`             | string            | See top-level `service_name`                                                                  |
| `actionName`              | string            | See top-level `action_name`                                                                   |
| `target.type`             | string            | The resource kind being acted on — e.g. `user`, `organization`, `project`                     |
| `target.id`               | number \| string? | The target resource identifier                                                                |
| `target.email`            | string?           | Target email when the resource is a user                                                      |
| `target.name`             | string?           | Target display name                                                                           |
| `target.orgId`            | string?           | Target's organization                                                                         |
| `authentication.type`     | string?           | How the actor authenticated — see [authentication types](#authentication-types)               |
| `authentication.provider` | string?           | Provider identifier for federated auth (e.g. `google`, `okta`, `saml`)                        |
| `requestParams`           | object?           | Non-sensitive, structured parameters describing the request. Secrets are never included       |
| `response.statusCode`     | number?           | HTTP status returned                                                                          |
| `response.errorMessage`   | string?           | Error message on `FAILURE` / `DENIED`, `null` otherwise                                       |
| `response.result`         | string?           | Short machine code for the outcome, e.g. `created`, `password_changed`, `invalid_credentials` |
| `metadata`                | object?           | Action-specific free-form context. Fields here are additive over time                         |

### Trace correlation

Each record also carries Google Cloud Logging trace fields (`logging.googleapis.com/trace`,
`logging.googleapis.com/spanId`, `logging.googleapis.com/trace_sampled`) and a
`logging.googleapis.com/labels` map for fast filtering. These are safe to ignore if you
don't use Cloud Logging — they never contain tenant data beyond what's already in
`audit_event`.

### Authentication types

`audit_event.authentication.type` describes how the actor proved their identity to
Trebellar. The current set of values:

| Value            | Meaning                                                                                |
| ---------------- | -------------------------------------------------------------------------------------- |
| `password`       | Email + password credentials                                                           |
| `oauth`          | OAuth / OIDC federated login (Google, Microsoft, etc.) — see `authentication.provider` |
| `saml`           | SAML 2.0 single sign-on — `authentication.provider` is always `saml`                   |
| `token_login`    | One-time signed login link (magic link)                                                |
| `password_reset` | Password-reset flow token                                                              |
| `session`        | Pre-existing authenticated session (e.g. for logout events)                            |

## Event catalog

The sections below enumerate every event Trebellar currently emits. The list will grow
over time — new events are appended rather than repurposing existing ones, so existing
consumers never break.

### `authentication` service

Events about how users sign in and manage their credentials.

| `actionName`    | Outcomes                       | When it fires                                                                                                                                                        |
| --------------- | ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `login`         | `SUCCESS`, `FAILURE`           | Any authentication attempt — password, OAuth, SAML, or magic-link. `FAILURE` includes bad credentials, missing SAML response, unregistered user, and provider errors |
| `logout`        | `SUCCESS`                      | User-initiated logout. `authentication.type` is always `session`                                                                                                     |
| `resetPassword` | `SUCCESS`, `FAILURE`, `DENIED` | Password-reset flow. `DENIED` is emitted on CSRF failures; `FAILURE` covers expired tokens, missing recipient email, and downstream errors                           |

### `users` service

Events about user-account lifecycle, scoped to the acting admin's organization.

| `actionName`     | Outcomes  | When it fires                                                                                                                                     |
| ---------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| `createUser`     | `SUCCESS` | A new user is created. `requestParams` includes `role`, `authMethod`, and `signupEmail` (boolean indicating whether an invitation email was sent) |
| `updateUser`     | `SUCCESS` | A user record is modified. `requestParams` includes the new `role` and `authMethod`                                                               |
| `deleteUser`     | `SUCCESS` | A user is removed from the organization                                                                                                           |
| `changePassword` | `SUCCESS` | A user changes their own password via the API                                                                                                     |

Only the outcomes currently emitted in production are listed above. Failures in
user-management flows are returned as HTTP errors before reaching the audit log
sink — this will tighten over time as more actions are instrumented for
`FAILURE`/`DENIED` events.

## Sample records

A successful SAML login:

```json
{
  "severity": "NOTICE",
  "message": "audit.authentication.login.success",
  "event_type": "audit",
  "service_name": "authentication",
  "action_name": "login",
  "outcome": "SUCCESS",
  "org_id": "org_01HF2Z8X5N",
  "request_id": "req_01HQW9K2B4",
  "httpRequest": {
    "requestMethod": "POST",
    "requestUrl": "/saml/callback",
    "remoteIp": "203.0.113.42",
    "userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 14_3) AppleWebKit/537.36",
    "status": 302
  },
  "audit_event": {
    "version": "1.0",
    "auditLevel": "ORG_LEVEL",
    "deployEnvironment": "production",
    "eventTime": "2026-04-19T14:22:07.418Z",
    "orgId": "org_01HF2Z8X5N",
    "sourceIPAddress": "203.0.113.42",
    "sessionId": "sess_01HQW9K2B4",
    "requestId": "req_01HQW9K2B4",
    "request": { "method": "POST", "route": "/saml/callback" },
    "userIdentity": {
      "type": "user",
      "id": 4219,
      "email": "alex@acme.example",
      "name": "Alex Doe",
      "orgId": "org_01HF2Z8X5N",
      "role": "admin"
    },
    "serviceName": "authentication",
    "actionName": "login",
    "target": {
      "type": "user",
      "id": 4219,
      "email": "alex@acme.example",
      "orgId": "org_01HF2Z8X5N"
    },
    "authentication": { "type": "saml", "provider": "saml" },
    "response": { "statusCode": 302, "errorMessage": null, "result": "SUCCESS" }
  }
}
```

A denied password-reset (CSRF mismatch):

```json
{
  "severity": "WARNING",
  "message": "audit.authentication.resetPassword.denied",
  "event_type": "audit",
  "service_name": "authentication",
  "action_name": "resetPassword",
  "outcome": "DENIED",
  "request_id": "req_01HQWB3TX0",
  "httpRequest": {
    "requestMethod": "POST",
    "requestUrl": "/reset-password",
    "remoteIp": "198.51.100.7",
    "status": 403
  },
  "audit_event": {
    "version": "1.0",
    "auditLevel": "APPLICATION_LEVEL",
    "deployEnvironment": "production",
    "eventTime": "2026-04-19T14:25:11.902Z",
    "requestId": "req_01HQWB3TX0",
    "request": { "method": "POST", "route": "/reset-password" },
    "serviceName": "authentication",
    "actionName": "resetPassword",
    "authentication": { "type": "password_reset" },
    "response": { "statusCode": 403, "errorMessage": "csrf_mismatch", "result": "DENIED" }
  }
}
```

A user-create event:

```json
{
  "severity": "NOTICE",
  "message": "audit.users.createUser.success",
  "event_type": "audit",
  "service_name": "users",
  "action_name": "createUser",
  "outcome": "SUCCESS",
  "org_id": "org_01HF2Z8X5N",
  "request_id": "req_01HQWC5JPZ",
  "httpRequest": {
    "requestMethod": "POST",
    "requestUrl": "/api/users",
    "status": 200
  },
  "audit_event": {
    "version": "1.0",
    "auditLevel": "ORG_LEVEL",
    "deployEnvironment": "production",
    "eventTime": "2026-04-19T15:02:44.311Z",
    "orgId": "org_01HF2Z8X5N",
    "requestId": "req_01HQWC5JPZ",
    "request": { "method": "POST", "route": "/api/users" },
    "userIdentity": {
      "type": "user",
      "id": 4219,
      "email": "alex@acme.example",
      "orgId": "org_01HF2Z8X5N",
      "role": "admin"
    },
    "serviceName": "users",
    "actionName": "createUser",
    "target": {
      "type": "user",
      "id": 5502,
      "email": "jamie@acme.example",
      "name": "Jamie Lee",
      "orgId": "org_01HF2Z8X5N"
    },
    "requestParams": {
      "role": "member",
      "authMethod": "saml",
      "signupEmail": true
    },
    "response": { "statusCode": 200, "errorMessage": null, "result": "created" }
  }
}
```

## Querying the logs

Because each file is plain NDJSON, any tool that can read line-delimited JSON works out
of the box.

Pull the last hour of failed logins with `jq`:

```bash
cat trebellar-audit-2026-04-19.ndjson \
  | jq -c 'select(.service_name == "authentication"
                 and .action_name == "login"
                 and .outcome == "FAILURE")'
```

Create an external table in BigQuery:

```sql
CREATE OR REPLACE EXTERNAL TABLE audit.trebellar_events
OPTIONS (
  format = 'NEWLINE_DELIMITED_JSON',
  uris = ['gs://acme-trebellar-audit-logs/audit-logs/*/*.ndjson']
);
```

## Troubleshooting

| Symptom                                      | Likely cause                                                                                               |
| -------------------------------------------- | ---------------------------------------------------------------------------------------------------------- |
| No file delivered for a given day            | No audit-worthy events occurred for your org that day. An empty file is **not** written                    |
| Delivery paused, error `InvalidCredentials`  | The client ID / secret you provided has been rotated or deleted on your side. Update it in the admin panel |
| Delivery paused, error `AccessDenied`        | The IAM principal lost `PutObject` on the target prefix                                                    |
| File contains events for other organizations | This cannot happen — files are strictly partitioned by `orgId`. Contact support                            |

Need an event that isn't on the list yet? Reach out at
[support@trebellar.com](mailto:support@trebellar.com) — the audit schema is
designed to accept new `service_name` / `action_name` pairs without requiring
a version bump.