Playbook detail
Inspect and edit a single trigger-based automation playbook — its conditions, actions, dry-run tester, and execution log.
Playbook detail
A playbook is a trigger-based automation rule: it watches one trigger_event on the
account timeline, evaluates a list of conditions when that event fires, and — if every
condition passes — runs an ordered list of actions. Playbooks are Pact's answer to
Salesforce Process Builder / Flow and Marketo Smart Campaigns, but stored as plain JSON
rows and evaluated in-process by core.playbook.PlaybookEngine so they are diffable,
peer-reviewable, and unit-testable.
The detail page is the single-record view backing GET /v1/playbooks/{id}. It exposes
four things: the rule definition, a dry-run tester, the enable/disable toggle, and the
execution log.
Marketing module + RBAC
Every playbook route is gated by Depends(verify_api_key) and
Depends(require_module("marketing")), and is tenant-scoped — the record is loaded with
WHERE id = :id AND tenant_id = :tid, so a leaked id cannot be read across tenants.
Anatomy of a playbook
A playbook row (playbooks table) carries a name, description, trigger_event, a JSON
conditions_json blob, a JSON actions_json blob, and two behavior flags. The API
expands the JSON blobs into conditions and actions arrays on read.
{
"id": "b3f1…",
"name": "Escalate enterprise closed-won",
"trigger_event": "deal.closed_won",
"conditions": [
{ "field": "revenue_usd", "op": "gte", "value": 100000 }
],
"actions": [
{ "action_type": "enroll_sequence", "params": { "sequence_id": 12 } },
{ "action_type": "notify_webhook", "params": { "url": "https://hooks…" } }
],
"enabled": true,
"run_once_per_account": true
}
Conditions are field op value triples. field is a dot-path into the evaluation
context (icp_score, revenue_usd, _event.event_type, _event.metadata.job_id). The
operator set is fixed in core.playbook._OPS: eq, ne, gt, gte, lt, lte,
contains, not_contains, exists, not_exists, in, not_in. All conditions must
pass (logical AND) for actions to fire.
Actions run in order. Four action_type values are implemented:
enqueue_job— enqueue a worker job (default taskenrich_company), with the account id injected into the payload.enroll_sequence— enqueue anenroll_sequencejob for the givensequence_id.set_stage— writecrm_stageon the company and record a stage-transition activity (changed_by="playbook").notify_webhook— enqueue afire_webhookjob carrying the event and account id.
run_once_per_account
Setting run_once_per_account: true makes the playbook idempotent per account — it will
not re-fire for an account it has already fired for, which is the safe default for
one-shot escalations. Unknown action_type values are silently ignored, so the engine is
forward-compatible with actions a newer build understands.
Editing
PATCH /v1/playbooks/{id} accepts a partial body — any of name, description,
trigger_event, conditions, actions, enabled, run_once_per_account. Only the
supplied fields are written; the response is the full re-read record. Toggling enabled
is the pause switch — a disabled playbook stays in the list but never fires.
Dry-run before you ship
POST /v1/playbooks/{id}/test evaluates the rule against a sample event and account row
without enqueuing any jobs or writing any rows. It returns per-condition pass/fail,
whether the event type matched the trigger, and which actions would fire:
curl -X POST /v1/playbooks/$ID/test \
-H "Authorization: Bearer $KEY" \
-d '{
"event": { "event_type": "deal.closed_won", "metadata": {} },
"account_row": { "revenue_usd": 250000, "crm_stage": "customer" }
}'
{
"trigger_event": "deal.closed_won",
"event_type_matches": true,
"conditions": [{ "field": "revenue_usd", "op": "gte", "value": 100000, "passed": true }],
"all_conditions_pass": true,
"actions_would_fire": [ … ]
}
Execution log
GET /v1/playbooks/{id}/runs returns rows from playbook_run_log — one per fired action,
with account_id, action_type, params_json, status, error, and fired_at. This
is the audit trail that replaces Salesforce Debug Logs: every firing, successful or failed,
is recorded here.