Audit log

Audit log API

The audit log records every authenticated mutating request against your organization — who did what, against which endpoint, when, and with what result — for security review and compliance. It's organization‑scoped and requires the owner or admin role.

See the matching dashboard page in Audit log for the click‑by‑click view, filtering, and CSV export.

What gets recorded

A row is written for every mutating HTTP request (POST, PUT, PATCH, DELETE) anywhere under /api. Read‑only requests (GET, HEAD, OPTIONS) are not logged. Each row captures the request envelope, not the business action:

FieldDescription
created_atWhen the request happened (ISO 8601, UTC)
methodHTTP method (POST, PUT, PATCH, DELETE)
pathRequest path with route params interpolated (e.g. /api/orgs/{orgId}/api-keys)
status_codeHTTP response status the caller received
latency_msServer processing time, in milliseconds
actor_user_idThe acting user's id, when JWT‑authenticated (else null)
actor_user_emailThe acting user's email, joined in for display (else null)
actor_api_keyThe acting API key's id, when key‑authenticated (else null)
ipClient IP (honors X-Forwarded-For)
user_agentThe request's User-Agent, if any

The request body is never stored — only a SHA‑256 hash of it is computed internally, so a security review can prove "the same payload was sent" without retaining any PII.

Actor attribution

The acting identity is resolved from how the request authenticated:

  • User (JWT): actor_user_email is the email and actor_user_id is set.
  • API key: actor_api_key holds the api_keys.id of the key (look it up against your keys); actor_user_email/actor_user_id are null.
  • Unauthenticated: all three actor fields are null (the request still gets logged with its status — useful for spotting failed/forbidden attempts).

List entries

GET /api/orgs/{orgId}/audit

Returns a paginated list, newest first. Authenticate with a user JWT + X-Org-Id; the path {orgId} must match your X-Org-Id or the request is rejected.

curl "https://api.telenow.ai/api/orgs/{orgId}/audit?limit=100" \
  -H "Authorization: Bearer eyJ…" \
  -H "X-Org-Id: {orgId}"
{
  "success": true,
  "data": {
    "events": [
      {
        "id": "…",
        "org_id": "…",
        "actor_user_id": "…",
        "actor_user_email": "[email protected]",
        "actor_api_key": null,
        "method": "POST",
        "path": "/api/orgs/…/api-keys",
        "status_code": 201,
        "latency_ms": 34,
        "ip": "203.0.113.7",
        "user_agent": "curl/8.4.0",
        "created_at": "2026-06-12T08:00:00Z"
      }
    ],
    "total": 1842
  }
}

Filters

All filters are optional and combine with AND. The result is capped at 200 rows per page — page through larger ranges with limit/offset (see pagination).

ParamTypeNotes
methodstringExact HTTP method; matched case‑insensitively (uppercased server‑side)
pathstringPrefix match — path=/api/orgs matches everything under that path. A % in the value is treated as a raw SQL LIKE pattern
sinceISO 8601Only entries at or after this time
untilISO 8601Only entries strictly before this time
limitintPage size (default 50, max 200)
offsetintRows to skip (default 0)
# All API-key creations/revocations in June, newest first
curl "https://api.telenow.ai/api/orgs/{orgId}/audit?path=/api/orgs/{orgId}/api-keys&since=2026-06-01T00:00:00Z" \
  -H "Authorization: Bearer eyJ…" \
  -H "X-Org-Id: {orgId}"

Export to CSV

GET /api/orgs/{orgId}/audit/export

Returns the filtered audit trail as a downloadable CSV (compliance teams ask for this in SOC 2 / ISO reviews). It accepts the same filters as the list endpoint, and is server‑capped at 50,000 rows per export — narrow with since/until for larger ranges. The file is named audit-YYYY-MM-DD.csv.

If the file is incomplete, the response says so rather than just stopping: X-Export-Truncated: true, plus X-Export-Rows (what you got) and X-Export-Total (what matched), and the filename gains -partial-<rows>-of-<total>. X-Export-Truncated is sent on every export, so false is a positive "this file is whole" rather than silence. The dashboard shows a notice too. See the campaigns export for the full description.

The CSV has 9 columns, in this order:

#ColumnNotes
1atTimestamp (RFC 3339)
2methodHTTP method
3pathRequest path
4statusHTTP status code
5latency_msLatency in milliseconds
6actor_emailActing user's email, or empty for key/anonymous
7actor_api_keyActing API key id, or empty
8ipClient IP, or empty
9user_agentRequest User-Agent, or empty
curl -L "https://api.telenow.ai/api/orgs/{orgId}/audit/export?since=2026-06-01T00:00:00Z" \
  -H "Authorization: Bearer eyJ…" \
  -H "X-Org-Id: {orgId}" \
  -o audit.csv

Tips

  • Because the log keys on the request path, you can answer "who created or revoked API keys?" by filtering path=/api/orgs/{orgId}/api-keys and looking at method (POST = create, DELETE = revoke).
  • Filtering by status isn't a query param, but failed attempts are still recorded — pull a range and filter status_code >= 400 client‑side to find rejected or forbidden requests.
  • For long‑term retention beyond what the dashboard shows, export to CSV on a schedule and load it into your SIEM/log store.

Troubleshooting

  • Empty results: confirm you're hitting the right {orgId} and that your X-Org-Id matches the path — a mismatch returns 403. Remember only mutating requests are logged, so a quiet org with read‑only traffic will have few rows.
  • 403 Forbidden: the audit log is owner/admin‑only. A developer or viewer key/JWT can't read it.
  • Missing actor: a null actor means the request was unauthenticated (or auth failed before the actor was resolved). For key‑authed rows, the actor appears under actor_api_key, not actor_user_email.

See also Compliance and Organizations, team & API keys.