Skip to content

Audit logs overview

The audit log is the workspace's tamper-evident record of who did what, from where, and whether it worked. It is the endpoint you reach for during a compliance review, an access certification, or a security investigation — and the one you point a SIEM or warehouse at for continuous export.

Every entry names an actor (who), a target (what they acted on), an event (what they did), an outcome (whether it succeeded), and a source (which surface the request came through). Where the event changed something, old_value and new_value carry the before and after.

Audit logs are read-only. There is no way to write, edit, or delete an entry through the API.

The audit log object

Attributes

  • id string (uuid)

    Unique identifier for this audit log record. This is the id you pass to Get an audit log.

  • event_id string (uuid)

    Identifier of the event the record describes, distinct from id. Use it as the idempotency key when you ingest entries into another system so a re-read never double-counts.

  • sequence_number integer

    An ordering number assigned to the entry as it was written. Useful as a stable tiebreaker when two entries share the same created_at.

  • event_name string

    The specific event, for example member.role_updated. Filterable — this is the field to pin when you know exactly which action you are hunting for.

  • category string

    The family the event belongs to. One of auth, member, role, settings, integration, webhook, security, or instance. Filter on category when you want a whole class of activity rather than one event name.

  • outcome string

    Whether the attempt succeeded. One of success or failure. Failed attempts are recorded, which is what makes this useful for detecting probing.

  • source string

    The surface the request came through. One of platform, api, graphql, auth, or system. api marks calls made with a token like yours; system marks actions Plane took on its own.

  • actor_type string

    What kind of principal acted. One of user, api_token, system, or anonymous.

  • actor_id string (uuid)

    The acting user. Nullable — system and anonymous actors have no id. It matches member_id on the member rosters.

  • actor_display_name , actor_email string

    The actor's name and email captured at the time of the event. They are snapshots: a later rename or email change does not rewrite past entries.

  • target_type string

    The kind of thing that was acted on, for example workspace_member.

  • target_id string

    Identifier of the thing that was acted on.

  • target_display_name string

    A human-readable label for the target, captured at the time of the event.

  • old_value , new_value any

    The state before and after the change. Both are nullable — an event that changes nothing, such as a sign-in, carries null on both sides.

  • reason string

    A short explanation recorded alongside the event. Most informative on outcome: failure entries.

  • metadata any

    Additional structured context. The shape varies by event_name, so read it defensively rather than typing it strictly.

  • ip_address string

    The client IP the request came from. Nullable — system events have none.

  • user_agent string

    The client's user agent string.

  • workspace_id string (uuid)

    The workspace the event belongs to.

  • project_id string (uuid)

    The project the event is scoped to, when it is scoped to one.

  • created_at string (date-time)

    When the event was recorded. This is the field the date-range filters work on.

Response200
json
{
  "id": "5c7f1a08-2b64-4d39-9e17-0a3b8c6d2f41",
  "event_id": "e0b41d97-6c23-4a8f-b512-9d7a0e3c5f68",
  "sequence_number": 48213,
  "event_name": "member.role_updated",
  "category": "role",
  "outcome": "success",
  "source": "platform",
  "actor_type": "user",
  "actor_id": "16c61a3a-512a-48ac-b0be-b6b46fe6f430",
  "actor_display_name": "Priya Raghavan",
  "actor_email": "priya@example.com",
  "target_type": "workspace_member",
  "target_id": "3e8a5d17-9c40-4b2f-81d6-4a7f2b9e0c53",
  "target_display_name": "Devansh Kapoor",
  "old_value": { "role": "member" },
  "new_value": { "role": "owner" },
  "reason": "",
  "metadata": { "workspace_slug": "my-team" },
  "ip_address": "203.0.113.42",
  "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36",
  "workspace_id": "d84c6f21-7a30-4e59-b6c8-1f9d5a2e7043",
  "project_id": null,
  "created_at": "2026-01-14T09:22:41.478363Z"
}
Response200
json
{
  "id": "9a2c4e76-1f58-4b03-8d69-5e0a7c3b1d24",
  "event_id": "b7f3da41-8c05-4e92-a37d-2b6f1c9e5804",
  "sequence_number": 48197,
  "event_name": "auth.sign_in",
  "category": "auth",
  "outcome": "failure",
  "source": "auth",
  "actor_type": "anonymous",
  "actor_id": null,
  "actor_display_name": "",
  "actor_email": "devansh@example.com",
  "target_type": "user",
  "target_id": "7f2b9e04-6c1d-4a58-9e3b-0d4c8a2f6b71",
  "target_display_name": "Devansh Kapoor",
  "old_value": null,
  "new_value": null,
  "reason": "invalid_credentials",
  "metadata": { "attempt": 3 },
  "ip_address": "198.51.100.7",
  "user_agent": "curl/8.6.0",
  "workspace_id": "d84c6f21-7a30-4e59-b6c8-1f9d5a2e7043",
  "project_id": null,
  "created_at": "2026-01-14T08:57:03.220914Z"
}

Endpoints

MethodPathDescription
GET/api/v2/workspaces/{slug}/audit-logs/List audit logs
GET/api/v2/workspaces/{slug}/audit-logs/{pk}/Get one audit log

Both require the workspaces.audit_logs:read scope. There is no POST, PATCH, or DELETE — entries are written by Plane and are not editable by any caller.

Querying for an investigation

The list endpoint takes the filters an investigation actually needs. They combine with AND:

QuestionQuery
What happened in this window??created_after=2026-01-01T00:00:00Z&created_before=2026-02-01T00:00:00Z
What did this person do??actor_id=16c61a3a-512a-48ac-b0be-b6b46fe6f430
Every permission change this quarter?category=role&created_after=2026-01-01T00:00:00Z
Failed attempts only?outcome=failure
Everything touching one object?target_type=workspace_member&target_id=3e8a5d17-9c40-4b2f-81d6-4a7f2b9e0c53
Activity from one address?ip_address=198.51.100.7
One specific action?event_name=member.role_updated

For a full export, add ?paginate=cursor and walk next_cursor — that skips the COUNT(*) on every page and stays stable as new entries arrive. Details on List audit logs.

Reading an entry

  • Snapshots, not joins. actor_display_name, actor_email, and target_display_name are recorded at event time. That is what makes the log evidential — the entry still names the person as they were, even after a rename or an offboarding.
  • Failures are first-class. outcome: failure entries are exactly what a security review is looking for. A burst of them from one ip_address with category: auth is the shape of a credential-stuffing attempt.
  • old_value and new_value are free-form. They describe the change for that event_name and are not a fixed schema. Render them, diff them, but do not require particular keys.
  • source separates humans from tokens. platform is the Plane app, api is a REST client, system is Plane acting by itself. Combining source: api with actor_type: api_token isolates automation.

Correlate a 409 with a settings change

If clients suddenly start getting 409 work_item_types_managed_at_workspace or work_item_types_managed_at_project, someone toggled the work item type mode. Query ?category=settings around the time the failures started to find the change and the actor behind it. See Work item type modes and Update workspace features.