---
title: "Everything a campaign did for one person, step by step: what was sent (subject and text), whether it was opened or clicked, what was skipped and why, waits, failures and retries"
method: GET
path: "/campaigns/people/{enrollmentId}/timeline"
tags: ["campaigns"]
---

# Everything a campaign did for one person, step by step: what was sent (subject and text), whether it was opened or clicked, what was skipped and why, waits, failures and retries

`GET /campaigns/people/{enrollmentId}/timeline`

## Request

- Base URL: `https://www.dench.com/api/v1`
- URL: `https://www.dench.com/api/v1/campaigns/people/{enrollmentId}/timeline`
- Auth: one of:
  - HTTP bearer
  - HTTP bearer

Everything a campaign did for one person, step by step: what was sent (subject and text), whether it was opened or clicked, what was skipped and why, waits, failures and retries.

## Path parameters

- `enrollmentId` string, required: From dench campaign people.

## Response `200`

Successful response

- object
  - `enrollment` object, required: The person in this campaign: status, lastError, repliedAt.
  - `sequence` object, nullable, required
  - `steps` object[], required: The campaign's steps in order, each with its _id.
    - `type` 'wait' | 'email' | 'linkedin_connection' | 'linkedin_message' | 'linkedin_visit' | 'linkedin_like_post' | 'linkedin_inmail' | 'condition' | 'wait_for_connection', required: Step type. condition (If / else) and wait_for_connection (Wait for them to accept) send nothing; they continue on yesNext or noNext and make the campaign a workflow.
    - `waitHours` number: wait steps: hours to wait before the next step.
    - `emailMailboxAccountId` string: Legacy email step sender. Pool campaigns choose senders in emailMailboxAccountIds on the campaign, not on individual steps.
    - `emailSubject` string: email steps: subject; {{Column}} merge tokens allowed.
    - `emailHtmlBody` string: email steps: HTML body. Sent instead of emailTextBody when both are set, so when you edit the text, update or drop this too.
    - `emailTextBody` string: email steps: plain-text body; used as the email when there is no emailHtmlBody.
    - `emailReplyToPrevious` boolean: email steps: thread under the previous email step instead of a new subject.
    - `onlyIf` 'always' | 'connected' | 'not_connected': email and linkedin_like_post only: always (default), connected or not_connected. InMail automatically skips connected people; do not set a condition on it.
    - `linkedinAccountId` string: Legacy LinkedIn step sender (also on wait_for_connection, and on a condition that checks linkedin_connected). Pool campaigns use the campaign's linkedinAccountIds instead.
    - `linkedinMessageBody` string: linkedin_connection: the invite note (optional, up to 300 chars; sent without it if LinkedIn refuses the note); linkedin_message: the required message (up to 3000 chars); linkedin_inmail: the required message (up to 1900 chars).
    - `linkedinMessageMode` 'skip_if_not_connected' | 'wait_until_connected': linkedin_message: what to do when the invite is still pending.
    - `connectionTimeoutHours` number: linkedin_message with linkedinMessageMode=wait_until_connected: wait 1-2160 hours for acceptance (default 720); after a connection step this mode is the default in a linear campaign. wait_for_connection: wait 24-2160 hours (1-90 days, default 720) before taking the Not accepted path.
    - `likeReaction` 'like' | 'celebrate' | 'support' | 'love' | 'insightful' | 'funny': linkedin_like_post only: the reaction to leave (default like).
    - `likeMaxPostAgeDays` integer: linkedin_like_post only: select the newest eligible original post from the last 1-365 days (default 30).
    - `likeSkipPreviousWait` boolean: linkedin_like_post only: before the immediately preceding wait starts, check for eligible posts. If none exist, skip that wait and the like step, keeping following waits. Defaults to false when omitted.
    - `likeSkipNextWait` boolean: linkedin_like_post only: skip consecutive following wait steps whenever the reaction step is skipped (default true). False keeps those waits.
    - `likeCount` integer: linkedin_like_post only: how many recent posts to react to, 1-3 (default 1).
    - `inmailSubject` string: linkedin_inmail only: optional subject, up to 200 characters; merge tokens allowed. Clear with an empty string.
    - `withdrawAfterHours` number, nullable: linkedin_connection only: withdraw a still-pending invitation sent by this campaign after 168-2160 hours (7-90 days). Omitted means off on creation; null turns it off on edit.
    - `key` string: Workflow steps: this step's id, unique in the campaign, which next, yesNext and noNext point at (not "end"). Left out, one is made.
    - `next` string: Workflow steps: where a step that isn't a condition or wait_for_connection goes; left out, the following step: another step's key, or "end" to stop there. Fixed once the campaign launches.
    - `yesNext` string: Workflow steps: condition and wait_for_connection only, required: where the Yes (Accepted) path goes: another step's key, or "end" to stop there. Fixed once the campaign launches.
    - `noNext` string: Workflow steps: condition and wait_for_connection only, required: where the No (Not accepted) path goes: another step's key, or "end" to stop there. Fixed once the campaign launches.
    - `check` 'linkedin_connected' | 'has_email' | 'opened_last_email' | 'field_value': condition only, required: linkedin_connected, has_email, opened_last_email (needs an email before it on every path) or field_value. Fixed once the campaign launches.
    - `conditionField` string: condition with check=field_value: the lead field to check, like "Job Title" or "Source" (the campaign itself changes Status, Last touch and LinkedIn connection). Fixed once the campaign launches.
    - `conditionOperator` 'is' | 'is_not' | 'is_empty' | 'is_not_empty' | 'contains': condition with check=field_value: is, is_not, contains, is_empty or is_not_empty (all case-insensitive). Fixed once the campaign launches.
    - `conditionValue` string: condition with check=field_value and is, is_not or contains: the text to compare with. Editable after launch.
  - `runs` object[], required: One run for each step the person has reached, in order.
    - `_id` string, required
    - `stepId` string, required: The step's _id, as in steps[].
    - `stepIndex` number, required: The step's place, from 0.
    - `type` 'wait' | 'email' | 'linkedin_connection' | 'linkedin_message' | 'linkedin_visit' | 'linkedin_like_post' | 'linkedin_inmail' | 'condition' | 'wait_for_connection', required: Step type. condition (If / else) and wait_for_connection (Wait for them to accept) send nothing; they continue on yesNext or noNext and make the campaign a workflow.
    - `status` 'queued' | 'running' | 'waiting' | 'retry' | 'sent' | 'skipped' | 'failed' | 'unknown', required: sent: done (a wait that finished is sent too). skipped: see result.reason. retry: will be tried again at retryAt. unknown: needs a look.
    - `scheduledAt` number
    - `startedAt` number
    - `waitUntil` number
    - `retryAt` number
    - `completedAt` number
    - `emailMessageId` string: The email this run sent, in emailMessages[].
    - `lastError` string
    - `timeoutAt` number: wait_for_connection: when it stops waiting for the accept.
    - `result` unknown
  - `emailMessages` object[], required: The emails those runs sent: subject, textBody, status, sentAt, firstOpenedAt, firstClickedAt.
  - `linkedinMessages` object[], required: The LinkedIn messages those runs sent: text, sentAt, deliveredAt, readAt, outreachStepRunId.

## Other responses

- `400`: Invalid request
- `401`: Missing or invalid authentication
- `404`: Operation or resource not found
- `500`: Server error

---

[API](https://skmtc.dev/dench/apis/dench-api.md) - [All operations](https://skmtc.dev/dench/apis/dench-api/llms.txt) - [OpenAPI document](https://skmtc.dev/dench/apis/dench-api/revisions/4a145b38f19c?raw)
