Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
375 changes: 375 additions & 0 deletions api/audit_log.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,375 @@
Audit Log
=========

The audit log records security-relevant changes to your Zammad system:
who changed what, and when. The audit log is read-only.

List
----

Required permission: ``admin.audit_log``

``GET``-Request sent: ``/api/v1/audit_logs``

The endpoint supports pagination. The default page size is ``500``.
Entries are returned ordered by ``id`` (ascending). Pass ``?sort_by=id``
and ``?order_by=DESC`` to return the newest entries first.

Response:

.. code-block:: json
:force:

# HTTP-Code 200 Ok

[
{
"id": 1,
"user_id": 3,
"user_fullname": "Christopher Miller",
"action_type": "create",
"auditable_type": "ChecklistTemplate",
"auditable_id": 1,
"auditable_name": "Onboarding",
"value_from": {},
"value_to": {
"name": "Onboarding",
"active": true,
"sorted_item_names": [
"Buy chair",
"Coffee briefing",
"Create accounts"
]
},
"source_ip": "172.19.0.1",
"preferences": {},
"created_at": "2026-07-23T09:14:51.650Z",
"updated_at": "2026-07-23T09:14:51.650Z"
},
{
"id": 2,
"user_id": 3,
"user_fullname": "Christopher Miller",
"action_type": "update",
"auditable_type": "Setting",
"auditable_id": 280,
"auditable_name": "ai_provider",
"value_from": {
"area": "AI::Provider",
"name": "ai_provider",
"title": "AI provider",
"options": {},
"frontend": true,
"description": "Defines if the AI provider is configured.",
"state_current": {
"value": true
},
"state_initial": {
"value": false
}
},
"value_to": {
"area": "AI::Provider",
"name": "ai_provider",
"title": "AI provider",
"options": {},
"frontend": true,
"description": "Defines if the AI provider is configured.",
"state_current": {
"value": false
},
"state_initial": {
"value": false
}
},
"source_ip": "172.19.0.1",
"preferences": {
"changed_attributes": [
"state_current"
]
},
"created_at": "2026-07-23T09:14:59.501Z",
"updated_at": "2026-07-23T09:14:59.501Z"
},
{
"id": 3,
"user_id": 3,
"user_fullname": "Christopher Miller",
"action_type": "update",
"auditable_type": "Role",
"auditable_id": 2,
"auditable_name": "Agent",
"value_from": {},
"value_to": {
"permissions": [
"admin.text_module"
],
"group_permissions": {
"2nd Level": [
"full"
],
"Service Desk": [
"full"
]
}
},
"source_ip": "172.19.0.1",
"preferences": {},
"created_at": "2026-07-23T09:16:30.188Z",
"updated_at": "2026-07-23T09:16:30.188Z"
},
{
"id": 4,
"user_id": 3,
"user_fullname": "Christopher Miller",
"action_type": "update",
"auditable_type": "Setting",
"auditable_id": 265,
"auditable_name": "pgp_integration",
"value_from": {
"area": "Integration::Switch",
"name": "pgp_integration",
"title": "PGP integration",
"options": {
"form": [
{
"tag": "boolean",
"name": "pgp_integration",
"null": true,
"display": "",
"options": {
"true": "yes",
"false": "no"
}
}
]
},
"frontend": true,
"description": "Defines if PGP encryption is enabled or not.",
"state_current": {
"value": false
},
"state_initial": {
"value": false
}
},
"value_to": {
"area": "Integration::Switch",
"name": "pgp_integration",
"title": "PGP integration",
"options": {
"form": [
{
"tag": "boolean",
"name": "pgp_integration",
"null": true,
"display": "",
"options": {
"true": "yes",
"false": "no"
}
}
]
},
"frontend": true,
"description": "Defines if PGP encryption is enabled or not.",
"state_current": {
"value": true
},
"state_initial": {
"value": false
}
},
"source_ip": "172.19.0.1",
"preferences": {
"changed_attributes": [
"state_current"
]
},
"created_at": "2026-07-23T09:17:20.469Z",
"updated_at": "2026-07-23T09:17:20.469Z"
}
]


Show
----

Required permission: ``admin.audit_log``

``GET``-Request sent: ``/api/v1/audit_logs/{id}``

Response:

.. code-block:: json
:force:

# HTTP-Code 200 Ok

{
"id": 3,
"user_id": 3,
"user_fullname": "Christopher Miller",
"action_type": "update",
"auditable_type": "Role",
"auditable_id": 2,
"auditable_name": "Agent",
"value_from": {},
"value_to": {
"permissions": [
"admin.text_module"
],
"group_permissions": {
"2nd Level": [
"full"
],
"Service Desk": [
"full"
]
}
},
"source_ip": "172.19.0.1",
"preferences": {},
"created_at": "2026-07-23T09:16:30.188Z",
"updated_at": "2026-07-23T09:16:30.188Z"
}

Search
------

Required permission: ``admin.audit_log``

The search endpoint accepts the Zammad search-backend ``query``
syntax. The simplest case is a literal substring on a single
indexed field such as ``auditable_name``, ``auditable_type`` or
``user_fullname``:

``GET``-Request sent: ``/api/v1/audit_logs/search?query={search-string}``

Response:

.. code-block:: json
:force:

# HTTP-Code 200 Ok

[
{
"id": 3,
"user_id": 3,
"user_fullname": "Christopher Miller",
"action_type": "update",
"auditable_type": "Role",
"auditable_id": 2,
"auditable_name": "Agent",
"value_from": {},
"value_to": {
"permissions": [
"admin.text_module"
],
"group_permissions": {
"2nd Level": [
"full"
],
"Service Desk": [
"full"
]
}
},
"source_ip": "172.19.0.1",
"preferences": {},
"created_at": "2026-07-23T09:16:30.188Z",
"updated_at": "2026-07-23T09:16:30.188Z"
}
]

To filter on a specific attribute rather than substring-match the
whole record, prefix the attribute name. You can even use the logical ``AND``
connector to narrow down the results:

``GET``-Request sent:
``/api/v1/audit_logs/search?query=auditable_type:Setting AND user_id:3``

.. note::

Search matches are case-sensitive and search only the indexed
attribute fields (``auditable_name``, ``auditable_type``,
``user_fullname`` and so on). The ``value_from`` and ``value_to``
payloads are not searchable.

.. note::

By default the response is a bare JSON array of matching entries.
Pass ``with_total_count=true`` on the URL (or ``with_total_count:
true`` in the body of a ``POST`` request) to wrap the response in
an object that also contains the ``total_count``. Send a ``POST``
request when the query is too long or complex for a URL.

Field Reference
---------------

``id``
Integer primary key of the audit log entry.

``user_id``
ID of the user that triggered the change. ``null`` when the entry
was written by a background job without a current user.

``user_fullname``
Full name of the user at the time the entry was written. Stored
separately so it remains readable after the user account is
removed.

``action_type``
Type of the recorded change. One of:

- ``create`` - a record was added.
- ``update`` - an existing record was modified.
- ``destroy`` - a record was removed.
- ``switch_to`` - a user took over another user's session via
``View from user's perspective``
- ``switch_back_to`` - the original session was resumed.

``auditable_id``
ID of the record that was changed.

``auditable_type``
Class name of the record that was changed (e.g. ``Role``,
``User``, ``Setting``, ``KnowledgeBase``, ``ChecklistTemplate``).

``auditable_name``
Display name of the changed record at the time the entry was
written. Stored separately so it remains readable after the record
itself is gone.

``value_from``
Object (JSON) holding the previous state of the audited
attributes. Empty (``{}``) on ``create`` entries.

``value_to``
Object (JSON) holding the new state of the audited attributes.
Empty (``{}``) on ``destroy`` entries.

``source_ip``
IP address that issued the underlying request. ``Rails console``
or ``Rails runner`` is stored when the entry was written from a
maintenance script.

``preferences``
Object (JSON) holding additional per-entry metadata. For
``update`` entries this contains a ``changed_attributes`` array
listing the attributes that actually changed.

``created_at``
Timestamp at which the entry was written. Audit log entries are
append-only.

``updated_at``
Same as ``created_at`` for audit log entries. Audit log entries
are append-only.

Lifecycle
---------

A scheduled task removes entries older than ``12 months`` every day.
Use the ``AuditLog.cleanup`` in the Rails console to trigger a cleanup manually.
1 change: 1 addition & 0 deletions index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ Zammad System Documentation
:caption: REST API

/api/intro
/api/audit_log
/api/calendar
/api/checklist
/api/generic-cti/index
Expand Down
Loading