Skip to content

My activity — what I did, and what was done to my account

GET
/account/activity

One newest-first list of the audit rows that name the caller, on either side: actor_user_ref = caller (things the caller did) or subject_user_ref = caller (things done to the caller’s account). The role discriminator says which.

WHY THIS IS NOT THE ADMIN AUDIT VIEWER WITH A FILTER. The admin surface (GET /admin/audit) is a log reader: every column, arbitrary actor/subject filters, and ip behind system.audit.pii.read (#425). Handing an ordinary caller a pre-filtered version of it would ship them a projection built for an auditor. This one answers a narrower question — “what happened involving me” — and its projection is cut to that question rather than trimmed down from the other one.

THE PROJECTION, AND WHY IT REFUSES WHAT IT REFUSES. It follows the /account/trash precedent: disclose the ACT, never the other party’s details.

metadata ships only on by_me rows. A by_me row is the caller’s own action read back, so its payload is a record of something they did and already knew. An on_my_account row is somebody ELSE’s action, and there the event type plus the time IS the disclosure: you are told that your account was disabled and when, which is the fact you need in order to ask about it. The metadata of an administrative action is that administrator’s working note — the free-text reason on a status change, the capability and team_id on a grant — and volunteering it here would be widening by convenience, the same move the trash listing refuses when it declines to name the deleter.

For the same reason on_my_account rows carry NO actor identity. The trash projection returns whether the caller may undo a delete and never who performed it; this returns that an event happened to the caller and never who caused it. Neither act disclosed the identity of the person who took it, and an account page is not the place to start.

NO ip AND NO user_agent, IN EITHER ROLE. Not gated — absent. /account/sessions already owns “where has my account been signed in from”, with its own shape and its own revocation controls, and #425’s capability rule is an admin-side rule about reading OTHER people’s personal data. Answering the same question a second way here would be a second surface to keep correct for no new answer.

Ordering and pagination are the (occurred_at, id) keyset, DESC, with the id tiebreak carrying the same comparison as the timestamp — audit rows written by one request share a timestamp to the microsecond, so the tiebreak is the common case rather than the rare one.

Rows are subject to the audit retention purge (ADR 0032), so this is a window on the log, not a permanent history.

limit
integer
default: 50 >= 1 <= 200
cursor
string

Opaque token from a previous response’s next_cursor.

A page of the caller’s own activity.

object
items
required
Array<object>

One audit row that names the caller, projected for the caller rather than for an auditor (#600). See GET /account/activity for why each omission is an omission.

object
id
required

The audit row id. Carried so the page can key rows and so a support conversation can name one exactly; it is not a handle to anything the caller can fetch.

string format: uuid
event_type
required

The raw dotted event type (user.password_changed, admin.asset.soft_deleted, …). Sent raw, not as a pre-rendered sentence, because the client already owns the translations and a server-side sentence would be one language baked into an API. Clients MUST render an unknown type as a readable fallback rather than a raw payload — new types land continuously and a page that only knows today’s list must not degrade to a JSON dump.

string
occurred_at
required
string format: date-time
role
required

by_me — the caller is the actor. on_my_account — the caller is the subject and somebody (or something) else acted.

A row where the caller is BOTH actor and subject is by_me: changing your own password is an act, not something that happened to you, and reporting it as the latter would make a self-service change read like an intrusion.

Rows with no actor at all (a system sweep, an expiry) reach the caller only as subject, so they are on_my_account — which is the honest reading: nobody the caller can ask did it.

string
Allowed values: by_me on_my_account
metadata

The event’s payload — PRESENT ONLY ON by_me ROWS, where it is a record of the caller’s own action. Absent, not empty, on on_my_account rows: an empty object would claim the event had no detail, when the truth is that its detail belongs to whoever acted. See the endpoint description.

Shapes are per-event-type and are not part of this contract; clients read the keys they recognise and ignore the rest.

object
key
additional properties
any
next_cursor

Opaque token for the next page; omitted on the final page. Pass back unchanged as the cursor query parameter.

string
nullable

Malformed request

object
error
required

Human-readable error summary

string
Example
the request could not be completed

Authentication required, missing, or invalid

object
error
required

Human-readable error summary

string
Example
the request could not be completed
Example
{
"error": "authentication required: sign in and retry with a valid session or API token"
}

Unexpected server error

object
error
required

Human-readable error summary

string
Example
the request could not be completed