Observing outcomes
Task management is in preview and available in the v1alpha2 API. Requests, responses, and activity-log payloads described in this guide may change without notice.
Once a review has been requested, reassigned, cancelled, or completed, you need a way to find out. This page covers the three ways to observe review-task outcomes, in order of how stable each one is: notifications, polling the check, and the activity log.
Notifications: the primary channel
The stable, public channel is ListNotifications (v1), covered in full in Polling for Notifications. Three notification types are specific to review tasks:
| Type | Emitted when |
|---|---|
notification.client.review_task_assigned.v1 | A review task is created and assigned to a reviewer. |
notification.client.review_completed.v1 | A review task is completed with a decision. |
notification.client.review_cancelled.v1 | A review task is cancelled. |
review_task_assigned.v1 carries check, assignee, and creator (the user who requested the review). review_completed.v1 carries check, assignee, creator, and review_decision. review_cancelled.v1 carries check, assignee, cancelled_by (empty for system-initiated cancellations), and cancel_reason (a machine-readable code such as OM_HIT_RESPAWN, empty for user-initiated cancellations, where cancelled_by carries the actor instead).
These are public, stable notification types, subject to the same guarantees as the rest of the notification system. Prefer this channel for anything that drives your own workflow — assignment inboxes, completion webhooks, audit trails.
The lightweight alternative: polling composite_status
If you do not need per-event granularity — only "has this check's review settled, and how" — polling Check.composite_status is simpler than consuming notifications. REVIEW_REQUESTED means a review is outstanding; REVIEW_APPROVED or REVIEW_REJECTED means it has settled. See Concepts and the task model for the full composite_status enum.
This trades timeliness for simplicity: you only learn a review completed the next time you poll the check, rather than as it happens.
The activity log
For a full audit trail of who did what to a review task — not just the current state, but the sequence of actions that produced it — use SearchActivityLogs (preview):
POST /v1alpha2/activityLogs:search
curl -X POST "https://api.thirdfort.dev/client/api/v1alpha2/activityLogs:search" \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"resources": ["organizations/{org_id}/teams/{team_id}/checks/{check_id}"],
"actionKeys": ["manualTask.create", "manualTask.update", "manualTask.complete", "manualTask.cancel"]
}'
resources takes exact resource names — the checks you want logs for, not a wildcard. action_keys scopes the search to specific event types; the four events relevant to task management are manualTask.create, manualTask.update, manualTask.complete, and manualTask.cancel. These action keys are not review-exclusive: every manual task type raises the same four events, so a check with document-review or income-verification tasks alongside its review task produces entries under the same action keys. Use task_type on the payload to distinguish them; the fields below that are meaningful only for REVIEW (decision, on_behalf_of) are absent or not applicable on other task types' entries.
Payload fields per event
The payload schema for each event below is preview and may change without notice — including field additions, removals, and renames. Do not build brittle parsing that assumes today's field set is final.
Event (action_key) | Payload fields |
|---|---|
manualTask.create | name, display_name, parent, status, task_type, assignee, creator_name, description, source, on_behalf_of |
manualTask.update | name, status, assignee, description, updated_fields, task_type, parent, requested_user_name, previous_assignee, reason, on_behalf_of |
manualTask.complete | name, decision, task_type, assignee, creator_name, notes, parent, on_behalf_of, requester_name |
manualTask.cancel | name, task_type, assignee, creator_name, parent, cancelled_by, notes, cancel_reason, on_behalf_of |
updated_fields (on manualTask.update) lists which fields changed in that event — check it before reading previous_assignee, which is only meaningful when updated_fields contains "assignee". reason is set only for system-initiated updates (for example "USER_DELETED", when an assignee's account was deleted and the task was unassigned as a result) and is empty for ordinary user-driven updates.
Actor semantics
Every ActivityLog entry carries an actor field: a bare resource name identifying who performed the action.
organizations/{organization}/users/{user}— a portal user.partners/{partner}/integrations/{integration}— an integration acting on its own credentials.SYSTEM— a Thirdfort automation (an auto-spawned or auto-cancelled task, for instance).
As with every other user reference in this API, actor is a resource name, not a display name. Resolve it with the same ListUsers recipe described in Building a task queue — build a name → display_name map once and look up each actor value against it, falling back to showing the raw resource name (or SYSTEM verbatim) for automation-driven entries.
When an action was performed via delegation, the activity log preserves both halves: actor is the integration that made the API call, and on_behalf_of (present on all four events) names the human the request was made for. The same user also appears in the event field a direct action would have filled — creator_name on manualTask.create, requested_user_name on manualTask.update, and assignee on manualTask.complete (a review may only be completed by its assignee, so the assignee at completion time is the completer). Neither half substitutes for the other — an audit trail needs both who acted and who decided.