# Verify API logs and the region audit log

Catalyst can sign two logs as they are written, so you can check them without trusting the pipeline that stored them:

- **The API log of each ID, agent and MCP server.** Each ID signs every Dapr API call it records, with its own identity.
- **The region audit log.** Each region signs a record of every authenticated call to its API, with the caller the credential proved. That covers calls from the console, the CLI and your own automation.

This page shows how to verify both, list the calls in the region audit log, and save signed logs as evidence. For what each record covers and how the records fit together with workflow history, see [Auditability](https://docs.diagrid.io/concepts/auditability).

## Before you start

- Install the [Diagrid CLI](https://docs.diagrid.io/references/catalyst/catalyst-cli-intro) and log in with `diagrid login`.
- [Turn signing on](#turn-on-signing) for the region. Both logs are off by default.
- The log store keeps signed lines for a few days, and the CLI reads at most 7 days back. To keep evidence longer, [save it](#save-evidence-for-an-auditor).

## Turn on signing

Both logs are off by default, and each region turns them on separately. They cost some CPU on every API call, and they add lines to the region's logs.

On a [self-managed region](https://docs.diagrid.io/operate/hosting/enterprise-self-hosted), set these values in the region's Helm values and upgrade the release:

```yaml
agent:
  config:
    featureflags:
      sidecar:
        api_log_signing: true   # each ID signs its API log
management:
  config:
    featureflags:
      management:
        audit_log: true         # the region writes and signs its audit log
```

You can turn on either one without the other. An ID starts signing the next time it restarts, so IDs already running keep writing unsigned lines until then.

On a region Diagrid manages, contact Diagrid support to turn signing on.

## Verification results

Each line, and the window as a whole, gets one of these results:

| Result | Means |
| --- | --- |
| `verified` | Signed by the identity shown and unchanged since. |
| `tampered` | A line was altered, injected or reordered, or was signed by an identity that isn't trusted or isn't the one expected. |
| `incomplete` | The signature is genuine, but lines it covers are missing. The output lists the missing ranges. A deleted line and a line lost in transit look the same. |
| `unverified` | No signature read so far covers the line. The newest lines wait a few seconds for the next signature, and lines at the edges of the window may have theirs outside it. On its own this is not evidence of tampering, but see [What verification can't show](#what-verification-cant-show) for the start of a window. |
| `unsigned` | The line was written while signing was off, before the ID restarted after signing was turned on, or by an ID with no identity. |

The verify commands exit non-zero when the result is `tampered` or `incomplete`, or when no record in the window verified. That makes them safe to use as a gate in a script.

## Verify an ID's API log

Verify the last hour of an ID's API log:

```bash
diagrid app logs <app> --project <project> --verify
```

```
Checked 21 lines from 1 stream(s) against the signing authorities your project's region publishes.

  verified        21  signed by the identity shown and unchanged

Verified records were written from 2026-10-29T09:00:03Z to 2026-10-29T09:59:41Z.

Stream afbf390ab7d36c3f2fc7a45e1f032afa
  signed by   spiffe://<region-trust-domain>/ns/prj-<project-id>/<app>
  result      verified, 18 of 18 records verified

Result: verified
```

Each `Stream` in the output is one run of the ID, and a restart starts a new one. The line count includes the signatures themselves, which is why it is higher than the record count. The `Verified records were written from` line is the stretch of time the result vouches for. Check that it reaches back to the start of the window you asked for; see [What verification can't show](#what-verification-cant-show).

The CLI checks that the log was signed by that ID in that project. Agents and MCP servers take the same flags:

```bash
diagrid agent logs <agent> --project <project> --verify
diagrid mcpserver logs <mcp-server> --project <project> --verify
```

Set the window with `--since` and `--until`, as an RFC 3339 time or a duration such as `24h`. For a machine-readable result, add `--output json`.

## Review the calls made to your regions

List every call to your regions' API in the last 24 hours, with each record's result:

```bash
diagrid audit list --region
```

```
TIMESTAMP                        ACTOR              METHOD  ROUTE                                                                 PROJECT  STATUS  AUTH METHOD  INTEGRITY
2026-11-02T14:48:25.376881861Z   jane@example.com   GET     /apis/cra.diagrid.io/v1beta2/projects/{projectId}/workflowexecutions  default  200     jwt          verified
```

The actor comes from the credential the call authenticated with, never from a header the caller sets. Calls refused by the role check are recorded too.

`audit list --region` always exits zero, and warns when the window as a whole is `tampered` or `incomplete`. A deleted record has no row of its own to flag, so use `audit verify` to check the log as a whole:

```bash
diagrid audit verify --since 24h
```

`audit verify` checks that every record was signed by one of your regions, and exits non-zero on the same results as `app logs --verify`.

## Save evidence for an auditor

Add `--export` to save the signed lines exactly as they were read, while verifying them:

```bash
diagrid app logs <app> --project <project> --verify \
  --since 2026-10-29T00:00:00Z --until 2026-10-30T00:00:00Z --export app-2026-10-29.jsonl

diagrid audit verify \
  --since 2026-10-29T00:00:00Z --until 2026-10-30T00:00:00Z --export audit-2026-10-29.jsonl
```

Save the signing authorities with them. These are the same ones [signed workflow archives](https://docs.diagrid.io/operate/project-operations/verify-workflow-history#verify-offline) verify against:

```bash
diagrid workflow archive trust-anchor --project <project> --out trust.pem
```

:::note
Save the trust bundle at the same time as the logs, and keep them together. Signing authorities rotate, so a bundle saved later may not match older logs. That shows up as `tampered` against an untrusted identity, so check the bundle before concluding a file was altered.
:::

## Verify offline

A saved file holds the lines and their signatures, so it verifies with no connection to Catalyst. Name the identity the log must be signed by:

```bash
diagrid app logs <app> --verify --file app-2026-10-29.jsonl --trust-anchor trust.pem \
  --app-id <app> --namespace prj-<project-id> --org <org-id>

diagrid audit verify --file audit-2026-10-29.jsonl --trust-anchor trust.pem --org <org-id>
```

The namespace and organization ID are in the `signed by` line and in the output of the original verification. The organization ID matters because each signature names the organization it was written for, so lines can't be moved from one organization's log into another's.

## What verification can't show

- **That a call happened as logged.** A signature proves which ID or region wrote a line, not that what it wrote is true. This is the same limit as [verifiable execution](https://docs.diagrid.io/concepts/verifiable-execution).
- **The end of a log, or a whole log.** Deleting the last records of an ID's run, or every line of one run, leaves nothing to compare against. A run that ended cleanly finishes with a final signature, so a run that stops without one is worth asking about.
- **The start of a window.** Records removed from the beginning of the window you read, together with their signatures, look like a window that starts later, and the rest still verifies. Hours can go from the front of a day's window this way. Check that the `Verified records were written from` time reaches back to the start of the window you asked for. Records removed from inside the first signed range, without its signature, show as `unverified` rather than `incomplete`.
- **Confidentiality.** The logs are signed, not encrypted.

## Reference

- [`diagrid app logs`](https://docs.diagrid.io/references/catalyst/cli-reference/app/logs), [`diagrid agent logs`](https://docs.diagrid.io/references/catalyst/cli-reference/agent/logs) and [`diagrid mcpserver logs`](https://docs.diagrid.io/references/catalyst/cli-reference/mcpserver/logs)
- [`diagrid audit`](https://docs.diagrid.io/references/catalyst/cli-reference/audit), including `audit list` and `audit verify`
- [Verify workflow history](https://docs.diagrid.io/operate/project-operations/verify-workflow-history)
