> ## Documentation Index
> Fetch the complete documentation index at: https://docs.sente.run/llms.txt
> Use this file to discover all available pages before exploring further.

# Audit trail

> Query the insert-only record of every credential read, session hand-off, and connection change in your org — and know exactly what each action means.

Sente holds passwords, TOTP seeds, and logged-in browser sessions on your behalf. The audit trail is how you answer the question that follows from that: **who touched them, and when.** Every sensitive act — a credential read in plaintext, a session handed to a caller, a portable session exported, a connection revoked, a run taken over by a person — writes an event you can query.

The trail is **insert-only**. There is no endpoint to edit or delete an event, events outlive the resources they describe, and they are scoped to your organization. `meta` carries descriptive fields only — never a password, never a TOTP seed, never session state.

<Note>
  This is the pull-based counterpart to [webhooks](/events/webhooks). Run *outcomes* (`completed`, `failed`, `blocked`) are not audit events — they live on the run and on the `run.*` webhooks. The audit trail records deliberate acts on credentials, sessions, accounts, and takeover.
</Note>

## Query it

`GET /v1/audit-events`. There is no SDK or CLI wrapper for this endpoint yet — it is a plain authenticated GET.

<CodeGroup>
  ```bash cURL theme={null}
  # Everything sensitive in the last 24 hours
  curl -G https://api.sente.run/v1/audit-events \
    -H "Authorization: Bearer $SENTE_API_TOKEN" \
    --data-urlencode "since=$(date -u -v-1d +%Y-%m-%dT%H:%M:%SZ)" \
    --data-urlencode "limit=200"

  # Just the exports — a storageState is a bearer credential for a logged-in account
  curl -G https://api.sente.run/v1/audit-events \
    -H "Authorization: Bearer $SENTE_API_TOKEN" \
    --data-urlencode "action=session.export"
  ```

  ```ts TypeScript theme={null}
  type AuditEvent = {
    id: string;
    action: string;
    subjectType: "registration" | "identity" | "run" | null;
    subjectId: string | null;
    meta: Record<string, unknown> | null;
    createdAt: string;
  };

  const since = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString();
  const res = await fetch(
    `https://api.sente.run/v1/audit-events?since=${encodeURIComponent(since)}&limit=200`,
    { headers: { authorization: `Bearer ${process.env.SENTE_API_TOKEN}` } },
  );
  if (!res.ok) throw new Error(`audit-events → ${res.status}`);
  const events = (await res.json()) as AuditEvent[];

  const sensitive = new Set(["credentials.read", "session.export", "connection.delete"]);
  for (const e of events.filter((e) => sensitive.has(e.action))) {
    console.log(e.createdAt, e.action, e.subjectType, e.subjectId);
  }
  ```

  ```python Python theme={null}
  import json
  import os
  import urllib.parse
  import urllib.request
  from datetime import datetime, timedelta, timezone

  since = (datetime.now(timezone.utc) - timedelta(days=1)).isoformat()
  query = urllib.parse.urlencode({"since": since, "limit": 200})
  req = urllib.request.Request(
      f"https://api.sente.run/v1/audit-events?{query}",
      headers={"authorization": "Bearer " + os.environ["SENTE_API_TOKEN"]},
  )
  with urllib.request.urlopen(req) as resp:
      events = json.load(resp)

  SENSITIVE = {"credentials.read", "session.export", "connection.delete"}
  for e in events:
      if e["action"] in SENSITIVE:
          print(e["createdAt"], e["action"], e["subjectType"], e["subjectId"])
  ```
</CodeGroup>

### Parameters

| Param    | Type          | Description                                                                                                   |
| -------- | ------------- | ------------------------------------------------------------------------------------------------------------- |
| `action` | string        | Exact match on one action value, e.g. `credentials.read`. An unknown value is not an error — it returns `[]`. |
| `since`  | ISO timestamp | Only events at or after this instant.                                                                         |
| `limit`  | int 1–200     | Default 50. Newest first.                                                                                     |

Response `200` — an array, newest first:

```json theme={null}
[
  {
    "id": "aud_4c3b2a1908f7e6d5c4b3a2918f7e6d5c",
    "action": "session.export",
    "subjectType": "registration",
    "subjectId": "reg_1c2d3e4f5a6b708192a3b4c5d6e7f801",
    "meta": null,
    "createdAt": "2026-07-20T11:42:07.884Z"
  },
  {
    "id": "aud_9182a3b4c5d6e7f80192a3b4c5d6e7f8",
    "action": "connection.create",
    "subjectType": "registration",
    "subjectId": "reg_1c2d3e4f5a6b708192a3b4c5d6e7f801",
    "meta": { "appOrigin": "https://app.example.com", "hasTotp": true },
    "createdAt": "2026-07-20T11:05:19.302Z"
  }
]
```

There is no pagination cursor: page by moving `since` backwards and raising `limit`. There is no endpoint to fetch a single event by id, and another org's events are never returned.

## Every action Sente records

This is the complete list of action values the API emits today.

| Action                | Subject      | `meta`                            | What it means                                                                                                                                                                                            |
| --------------------- | ------------ | --------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `registration.create` | registration | `{ credentialOrigin, appOrigin }` | An account row was opened for an app so Sente could sign up. `credentialOrigin` is `generated` (Sente made the password) or `supplied` (you did).                                                        |
| `connection.create`   | registration | `{ appOrigin, hasTotp }`          | You delegated an account you already own. Its credentials were vaulted; `hasTotp` says whether an authenticator seed came with them. Also written when you re-connect to rotate credentials.             |
| `connection.revoke`   | registration | —                                 | Delegated access was withdrawn. The agent can no longer log in; the vault is kept so re-connecting re-enables it.                                                                                        |
| `connection.delete`   | registration | —                                 | The connection was revoked **and** its vaulted password and TOTP seed were purged. Sente holds nothing for that account afterwards.                                                                      |
| `credentials.read`    | registration | —                                 | A username and password were returned in plaintext via `GET /v1/registrations/:id/credentials`. Sente-created accounts only — connected accounts are write-only and return `403 CREDENTIALS_WRITE_ONLY`. |
| `credentials.write`   | registration | —                                 | The vault was overwritten via `PUT /v1/registrations/:id/credentials`. This changes what Sente stores, not the password at the app.                                                                      |
| `session.open`        | identity     | `{ registrationId }`              | A live browser already signed in to the account was handed to a caller (`POST /v1/registrations/:id/session`).                                                                                           |
| `session.close`       | identity     | —                                 | That live session was stopped and released.                                                                                                                                                              |
| `session.export`      | registration | —                                 | Cookies and local storage left Sente as a portable Playwright `storageState`. Treat this as the highest-signal event in the trail: the export is a bearer credential for a logged-in account.            |
| `run.intervene`       | run          | —                                 | A person paused a live run to take manual control of the browser.                                                                                                                                        |
| `run.resume`          | run          | —                                 | A blocked run was handed back to the agent.                                                                                                                                                              |
| `run.abort`           | run          | —                                 | A person stopped a run.                                                                                                                                                                                  |
| `identity.delete`     | identity     | `{ email }`                       | An identity and everything under it — accounts, runs, messages, scoped webhooks — was hard-deleted. The event keeps the address, which no longer exists anywhere else.                                   |

<Warning>
  `meta` never contains secret values, and neither does any other field. A `credentials.read` event tells you a read happened and against which account — it does not contain the credentials. Nor does it identify an individual actor: API keys are org-scoped, so an event records the organization, the action, and the subject — not which person or key performed it.
</Warning>

## In the dashboard

[app.sente.run](https://app.sente.run) → **Activity** → the **Security** tab renders the same trail in plain English — "Credentials read", "Logged-in session exported" — with the sensitive actions highlighted. It is the fastest way to answer "did anything touch our accounts this week" without writing a query.

## Practical uses

<AccordionGroup>
  <Accordion title="Alert on credential and session exposure">
    Poll `action=credentials.read`, `action=session.export`, and `action=connection.delete` on a schedule with `since` set to your last poll, and forward anything new to your own alerting. Those three are the events where account access left Sente's control.
  </Accordion>

  <Accordion title="Reconstruct what happened to one account">
    Pull a window with `since`, then filter client-side on `subjectId` equal to the account's `reg_…` id. Because the trail is insert-only and events outlive their resources, this still works after the connection was deleted.
  </Accordion>

  <Accordion title="Prove a delegation was withdrawn">
    `connection.revoke` and `connection.delete` are the durable record that access was withdrawn — useful when the account owner asks for evidence. Pair with the connection's `revokedAt` field, which reflects current state rather than history.
  </Accordion>
</AccordionGroup>

## Errors

| Status | Body                           | Meaning                                                                                |
| ------ | ------------------------------ | -------------------------------------------------------------------------------------- |
| `400`  | zod issues array               | A malformed `since` (not a date) or a `limit` outside 1–200.                           |
| `401`  | `{"error": "missing api key"}` | No `Authorization: Bearer` or `x-api-key` header.                                      |
| `401`  | `{"error": "invalid api key"}` | The key is wrong or revoked. Mint a new one at [app.sente.run](https://app.sente.run). |

## Next steps

<CardGroup cols={2}>
  <Card title="Security model" icon="shield-check" href="/trust/security">
    Where credentials live, how they are encrypted, and who can read them.
  </Card>

  <Card title="Sessions" icon="monitor-play" href="/accounts/sessions">
    What `session.open` and `session.export` actually hand out.
  </Card>

  <Card title="Webhooks" icon="webhook" href="/events/webhooks">
    Push events for mail and run state, as opposed to this pull-based trail.
  </Card>

  <Card title="Audit events API reference" icon="terminal" href="/api-reference/audit-events">
    The endpoint contract in reference form.
  </Card>
</CardGroup>
