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:
| Field | Description |
|---|---|
created_at | When the request happened (ISO 8601, UTC) |
method | HTTP method (POST, PUT, PATCH, DELETE) |
path | Request path with route params interpolated (e.g. /api/orgs/{orgId}/api-keys) |
status_code | HTTP response status the caller received |
latency_ms | Server processing time, in milliseconds |
actor_user_id | The acting user's id, when JWT‑authenticated (else null) |
actor_user_email | The acting user's email, joined in for display (else null) |
actor_api_key | The acting API key's id, when key‑authenticated (else null) |
ip | Client IP (honors X-Forwarded-For) |
user_agent | The 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_emailis the email andactor_user_idis set. - API key:
actor_api_keyholds theapi_keys.idof the key (look it up against your keys);actor_user_email/actor_user_idarenull. - 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).
| Param | Type | Notes |
|---|---|---|
method | string | Exact HTTP method; matched case‑insensitively (uppercased server‑side) |
path | string | Prefix match — path=/api/orgs matches everything under that path. A % in the value is treated as a raw SQL LIKE pattern |
since | ISO 8601 | Only entries at or after this time |
until | ISO 8601 | Only entries strictly before this time |
limit | int | Page size (default 50, max 200) |
offset | int | Rows 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:
| # | Column | Notes |
|---|---|---|
| 1 | at | Timestamp (RFC 3339) |
| 2 | method | HTTP method |
| 3 | path | Request path |
| 4 | status | HTTP status code |
| 5 | latency_ms | Latency in milliseconds |
| 6 | actor_email | Acting user's email, or empty for key/anonymous |
| 7 | actor_api_key | Acting API key id, or empty |
| 8 | ip | Client IP, or empty |
| 9 | user_agent | Request 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-keysand looking atmethod(POST= create,DELETE= revoke). - Filtering by
statusisn't a query param, but failed attempts are still recorded — pull a range and filterstatus_code >= 400client‑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 yourX-Org-Idmatches the path — a mismatch returns403. 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
nullactor means the request was unauthenticated (or auth failed before the actor was resolved). For key‑authed rows, the actor appears underactor_api_key, notactor_user_email.
See also Compliance and Organizations, team & API keys.