Audit logs API
Audit Logs in Document Management SDK is an implementation case that collects, stores, and reads file, user, and administrative activity through the Document Governance SDK integration. To see how Storage and Editor activity is linked into a single audit trail, see Governance Integration.
Two read-only endpoints over the tenant's audit trail: a paged listing and a CSV export of a whole date range.
Base URL and authorization
/api/external/v1/admins/audit-logs
These endpoints require an API Key issued to an account holding the ADMIN role. Authorization is by role alone -
the key's scope is not examined. Both endpoints are read-only, so a key holding only api:read behaves identically
to one holding api:write.
The tenant is fixed by the server from the calling key. There is no request parameter that selects another tenant.
The tenant is resolved from the request host, so call the domain the key was issued for; calling a different domain
fails with API_KEY_004.
A deployment running with governance.audit.enabled=false does not register these endpoints at all, and every call
returns 404. A 404 here means the feature is off, not that the path is wrong.
Where the data comes from
Audit events are not stored in the Drive database. They live on the governance server, and Drive proxies the governance query API. Governance authentication is handled server-side, so a caller needs nothing beyond the Drive API Key.
One consequence is worth knowing before you build against these endpoints: a filter value outside the governance
vocabulary comes back as 500, not 400. Governance rejects the value with a 400, and Drive wraps that into
RESPONSE_001. Keep every filter inside the values listed below.
Filter values
eventCategory
| Value | Covers |
|---|---|
LOGIN | Sign-in, sign-out, account lock state |
FILE_FOLDER | File and folder actions |
SHARE | Sharing |
USER_MANAGEMENT | User and administrator account management |
SETTING | Configuration changes |
eventCategoryTwo or more values return 200 with zero results rather than an error, which is indistinguishable from "nothing
happened". Measured on 2026-09-14: eventCategory=LOGIN&eventCategory=SHARE returned totalCount of 0. To cover
several categories in one call, expand them into their eventTypes instead.
channel
| Value | Covers |
|---|---|
WEB | The web interface |
API | The external API |
MCP | MCP |
SYSTEM | The system, with no user request behind it |
outcomeStatus
| Value | Covers |
|---|---|
SUCCESS | The action succeeded |
FAIL | The action failed |
eventType
Forty-one values, each belonging to one category.
| Category | Types |
|---|---|
LOGIN | LOGIN, LOGOUT, ACCOUNT_LOCKED_STATUS_CHANGE |
FILE_FOLDER | FILE_CREATE, FILE_UPLOAD, FILE_DOWNLOAD, FILE_PREVIEW, FILE_OPEN, FILE_EDIT, FILE_DELETE, FILE_PERMANENT_DELETE, FILE_RESTORE, FILE_RENAME, FILE_MOVE, FILE_COPY, FOLDER_CREATE, FOLDER_DOWNLOAD, FOLDER_DELETE, FOLDER_PERMANENT_DELETE, FOLDER_RESTORE, FOLDER_RENAME |
SHARE | SHARE_CREATE, SHARE_PERMISSION_CHANGE, SHARE_REMOVE |
USER_MANAGEMENT | USER_CREATE, USER_DELETE, USER_PERMANENT_DELETE, USER_RESTORE, USER_INFO_UPDATE, USER_TWOFACTOR_RESET, USER_PASSWORD_CHANGE, USER_STATUS_CHANGE, ADMIN_CREATE, ADMIN_DELETE, ADMIN_PERMANENT_DELETE, ADMIN_RESTORE, ADMIN_INFO_UPDATE, ADMIN_PASSWORD_CHANGE, ADMIN_STATUS_CHANGE |
SETTING | SETTING_UPDATE, SETTING_ALLOWED_IP_CHANGE |
A successful sign-in and a failed one are both recorded as LOGIN; the difference is outcomeStatus. Only sign-out
gets its own type, LOGOUT. Filtering on LOGIN therefore returns successes and failures together.
Dates
Request dates are yyyy-MM-dd. A range runs from 00:00 on from up to 00:00 on the day after to, so the end date
is included. Values in the response are yyyy-MM-dd HH:mm:ss.SSS in Asia/Seoul.
List audit events
GET /api/external/v1/admins/audit-logs
Reads the calling key's tenant by page number. Ordering is fixed to occurredAt descending; there is no sort
parameter.
| Parameter | Type | Required | Description |
|---|---|---|---|
pi | integer | Yes | Page number, from 1 |
ps | integer | Yes | Page size, 1 to 200. A larger value is clamped to the governance ceiling of 200 |
actorEmail | string | No | Actor email, matched exactly. Partial search is not supported |
from | string | No | Start date, inclusive |
to | string | No | End date, inclusive |
eventCategory | string | No | One category value |
eventTypes | string[] | No | Event types, repeated once per value |
channel | string | No | WEB, API, MCP, or SYSTEM |
outcomeStatus | string | No | SUCCESS or FAIL |
eventTypes is repeated rather than comma-joined: eventTypes=FILE_RENAME&eventTypes=FILE_MOVE. There is no limit
on how many you send; the practical ceiling is the request URL length, 8 KB on a default web server.
curl -X GET "https://drive.example.com/api/external/v1/admins/audit-logs?pi=1&ps=20&from=2026-09-01&to=2026-09-14&eventCategory=FILE_FOLDER&eventTypes=FILE_RENAME&eventTypes=FILE_MOVE" \
-H "Authorization: Bearer replace-with-your-api-key" \
-H "Content-Type: application/json"
{
"result": true,
"code": 200,
"data": [
{
"eventId": "019b2f58-7f3a-7c21-8e90-4c1f2f2e7840",
"occurredAt": "2026-09-14 14:11:03.510",
"eventType": "FILE_RENAME",
"eventCategory": "FILE_FOLDER",
"actorType": "HUMAN",
"actorName": "First user",
"actorEmail": "user1@example.com",
"objectType": "FILE",
"objectName": "Proposal.pptx",
"objectEmail": null,
"objectPath": "MY/Sales/2026",
"outcomeStatus": "SUCCESS",
"outcomeCode": null,
"channel": "WEB",
"clientIp": "198.51.100.20",
"tenantSeq": "3",
"tenantName": "Example tenant",
"change": {
"before": "Proposal_draft.pptx",
"after": "Proposal.pptx"
},
"targets": null
}
],
"totalCount": 1234,
"pageIndex": 1,
"pageSize": 20
}
The listing response carries no message. Paging values arrive as totalCount, pageIndex, and pageSize.
| Field | Type | Description |
|---|---|---|
eventId | string | Event identifier, issued by governance |
occurredAt | string | When it happened |
eventType | string | Event type, the value eventTypes matches on |
eventCategory | string | The category the type belongs to |
actorType | string | Actor kind; HUMAN |
actorName | string | Actor display name |
actorEmail | string | Actor email, the same value as the Drive account id |
objectType | string | Target kind: FILE, FOLDER, USER, or SETTING |
objectName | string | Target name - file or folder name, account name, and so on |
objectEmail | string | Target account email; present only when the target is an account, otherwise null |
objectPath | string | Target path relative to its parent folder. The leading segment is a location code: MY for My Drive, SHARE for the shared area |
outcomeStatus | string | SUCCESS or FAIL |
outcomeCode | string | Failure reason code, on failures only, otherwise null |
channel | string | Where the request came from |
clientIp | string | Client IP of the request |
tenantSeq | string | Tenant identifier, the governance scope id |
tenantName | string | Tenant name, the governance scope name |
change | object | Before and after as {before, after, summary}, on change events only |
targets | object[] | Target list, such as the recipients of a share, on the events that have one |
{
"result": false,
"code": 400,
"errorCode": "REQUEST_001",
"message": "[{msg=must not be null, field=pi}]"
}
Export audit events as CSV
GET /api/external/v1/admins/audit-logs/export
Downloads the whole matching date range as CSV. It takes the same filters as the listing but no paging parameters, and the body is raw CSV with no JSON envelope.
| Parameter | Type | Required | Description |
|---|---|---|---|
from | string | No | Start date, inclusive. Empty means 92 days before to |
to | string | No | End date, inclusive. Empty means now |
actorEmail | string | No | Actor email, matched exactly |
eventCategory | string | No | One category value |
eventTypes | string[] | No | Event types, repeated once per value |
channel | string | No | WEB, API, MCP, or SYSTEM |
outcomeStatus | string | No | SUCCESS or FAIL |
The range is capped at 92 days. Leaving both dates empty applies the last 92 days, and a wider span is rejected with 400. A calendar quarter is 89 to 92 days, so 92 covers any three-month window. For anything longer, split the range across several calls.
pi and ps are not used here and are ignored if sent. The response is the whole range, not a page of it.
curl -X GET "https://drive.example.com/api/external/v1/admins/audit-logs/export?from=2026-06-01&to=2026-09-01&eventCategory=SHARE" \
-H "Authorization: Bearer replace-with-your-api-key" \
-o audit-events.csv
| Response header | Value |
|---|---|
Content-Type | text/csv |
Content-Disposition | attachment; filename*=UTF-8''audit-events_20260601-20260901.csv |
Content-Length | Absent - the body is streamed in chunks, so the total size is not known up front |
The file name follows audit-events_{from}-{to}.csv.
schemaVersion,eventId,eventType,sourceEventType,occurredAt,origin.system,…,observedIp,ingestKeyId
1.0,019b2f58-7f3a-7c21-8e90-4c1f2f2e7840,FILE_RENAME,FILE_RENAME,2026-07-16T05:11:03.510Z,TFD,…,198.51.100.20,key-acme-01
The file is the governance format unchanged: UTF-8 with a BOM, CRLF line endings, RFC 4180 quoting, 44 columns whose order is part of the contract, and a leading apostrophe on values that would otherwise be read as a formula. It carries more columns than the listing response.
{
"result": false,
"code": 400,
"message": "감사 로그 내보내기 기간은 최대 92일입니다."
}
The export failures are the exception to the common envelope: they carry no errorCode.
The response is chunked. Once the first chunk is sent the status is already 200, so a later failure just ends the response early. A conforming HTTP client treats a missing final chunk as an error rather than accepting the truncated file, but the client is still responsible for confirming that what it received is complete.
Stream long ranges to disk. A row is roughly 1,450 bytes, so 100,000 rows is about 138 MB. Anything that materializes
the whole response in memory - a browser Blob, a string conversion - can fail on a long range. Write straight to a
file instead, with curl -o or a stream copy.
Concurrency is limited to ten exports at a time per server instance, counted across all tenants. The eleventh returns 429, and the listing endpoint is unaffected. A 429 is safe to retry.
{
"result": false,
"code": 429,
"message": "다른 내보내기가 진행 중입니다. 잠시 뒤 다시 시도해 주세요."
}
Errors
| Code | Status | Meaning |
|---|---|---|
API_KEY_004 | 401 | Authentication failed - missing header, unknown value, deactivated or expired key, deactivated account, or a different tenant's domain |
AUTH_009 | 403 | The calling key does not hold the ADMIN role |
REQUEST_001 | 400 | Validation failed. message carries the failing fields, such as a missing pi or ps, or one below 1 |
RESPONSE_001 | 500 | The governance query failed, including a filter value outside the governance vocabulary |
See Errors for the full list of codes the API returns.