> ## Documentation Index
> Fetch the complete documentation index at: https://heybtw.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# record_observation

> Record what you learned about people at one of your collabs: who met them, a note, whether they came, follow-ups, invites and nudges.

> [!WARNING]
>
> Writes to your heyBTW workspace. It appends observations about people on one of your own collabs. It writes no attendance status, RSVP, task, List or CRM record, and it sends nothing.

The marketer's question is "how do I keep what the team learned at the event?" Reach for this tool when the user debriefs person by person ("Marcus met Priya, she wants a security review in Q1", "Dana never came"), when you find a follow-up in the user's own CRM, or when an invite or a nudge you sent needs a record.

Follows the [heyBTW MCP rules](https://heybtw.com/docs/mcp/rules.md).

## When to use it

* "Marcus met Priya at the dinner. She is evaluating in Q1."
* "Dana never actually showed up, even though Luma says checked in."
* "I emailed Theo the deck on Tuesday, log it."
* "I sent Jane a personal invite on LinkedIn yesterday."
* After you sent a nudge to an owner through the user's chat or mail connector, with the message id it returned.

## Parameters

| Parameter | Type | Required | Notes |
| - | - | - | - |
| `channel_id` | string | One of `channel_id` or `event_name` | The collab, from list\_events. Wins over `event_name`. |
| `event_name` | string | One of `channel_id` or `event_name` | An ambiguous name records nothing and lists the candidates. |
| `observations` | list | Yes | 1 to 20 items, one per person. See the kinds below. |
| `client_request_id` | string | Yes | A fresh hyphenated UUID for this call. The same id replays the call. |
| `contract_version` | string | No | Omit for v1. Only v1 is advertised. |

Each item has `person` (the email this workspace holds for them) and any of these kinds. One item can carry several kinds; each becomes its own fact.

| Kind | Shape | Rule |
| - | - | - |
| `met_by` | list of at most 3 of `{"user_email": ...}` or `{"partner_domain": ...}` | Only when the user said who met them. Never infer it from who attended together. |
| `note` | string, at most 500 characters | A short text. Never echoed back, only its length. |
| `attended_confirmed` | `true` or `false` | A manual statement stored beside the platform's attendance. It never changes `attendance_status` or the check-in. `false` against a provider check-in is the valuable case. |
| `followed_up` | `{at, channel, source}` plus optional `by_email`, `crm_provider`, `crm_record_ref` (200), `summary` (300) | `channel`: email, call, meeting, note, sequence, other. `source`: `agent_crm_read` when you read it in the CRM, `manual` when the user said it. `crm_provider`: hubspot, salesforce, attio, other. |
| `invited` | `{at, channel, source}` plus optional `by`, `list_id`, `message_ref` (200), `note` (300) | `channel`: personal\_email, sequence, linkedin, partner, luma, other. `source`: `agent_send` or `manual`. |
| `nudged` | `{at, channel, source, message_ref}`, all four required | `channel`: chat, email, task\_tool, crm\_task, calendar, other. `source`: `agent_send` or `manual`. `message_ref` (500) is the proof the connector returned. |

Every `at` is RFC 3339 with its offset, for example `2026-10-13T09:30:00-05:00`. An `at` later than now is refused.

## Workflow

**1. Resolve the collab**

Take `channel_id` from list\_events. If the user named the event, `event_name` works, but an ambiguous name records nothing and lists the candidates.

**2. Check each person is in the workspace**

`person` must be an email this workspace holds. A person heyBTW has no record of is rejected; add them first with [import\_people](https://heybtw.com/docs/tools/import-people.md).

**3. Build one item per person**

Put only facts the user stated or you saw. A second follow-up or invite for the same person is another item.

**4. Generate a client\_request\_id and call once**

On a transport error, retry with the SAME `client_request_id`. A committed call comes back with `idempotent_replay: true` and is never repeated. Reusing the id for different observations is refused.

**5. Report recorded and rejected**

Say how many facts were recorded and name each rejected reference with its reason.

## Response

The success shape below is from the code (`tools/record_observation.py` and `observation-contract.json`), not a live call: this page was written against a production workspace, so nothing was recorded. `data[0]` holds:

| Field | Meaning |
| - | - |
| `recorded_count` | How many facts were stored. |
| `source` | The one source every recorded fact shares, `mixed` when they differ, `manual` when nothing was recorded. |
| `observed_by` | `{kind: "user", user_id}` for the member behind the credential, or null. |
| `people[]` | `{person, recorded[]}`. Each recorded fact has `observation_id`, `kind`, `observed_at`, `source`, and per kind: `met_by {kind, label}`, `note_chars`, `attended_confirmed`, `followed_up {at, channel, summary_chars?}`, `invited {at, channel, note_chars?}`. A nudge returns only its kind and source. |
| `rejected_count`, `rejected[]`, `rejected_truncated` | Each rejection is `{ref, reason, field}`; at most 10 are echoed. |
| `duplicate_met_by_collapsed` | How many repeated `met_by` entries were folded into one. |
| `idempotent_replay` | `true` when this call replayed an earlier committed one. |

Keep each `observation_id`: [save\_task](https://heybtw.com/docs/tools/save-task.md) takes it as `observation_ref` when a follow-up comes out of the debrief. Read observations back with [get\_event\_details](https://heybtw.com/docs/tools/get-event-details.md) (`include_contacts`), where notes, follow-up summaries and nudge proofs appear on the contact rows. A partner of the collab never sees them.

The validation error below is a real response, trimmed, from a call that sent a `channel_id` and no `observations`:

```json theme={null}
{
  "tool": "record_observation",
  "status": "error",
  "data": null,
  "error": {
    "code": "invalid_parameter",
    "message": "A tool parameter is invalid",
    "what_failed": "A tool parameter (`observations`) is required but was not provided.",
    "what_to_check": "The `observations` argument of this call.",
    "what_to_do": "Provide `observations` and retry.",
    "retryable": false,
    "remediation": null
  }
}
```

## Rules the agent must follow

* An invite is never an RSVP and never a touch. It never changes attendance, a registration or a funnel count. Record one only for a send that happened or that the user states, never from a draft or from list membership.
* Never record again an invite that activate\_list sent, or a List the user sent outside heyBTW that was recorded with activate\_list `destination_type: "external"`. activate\_list already logs each of those invites.
* A nudge is never a touch. It is your ask going out to the person's owner, recorded as `nudged`, never as `followed_up`, and only with the `message_ref` the connector returned. Without that proof it is a draft: record nothing.
* `followed_up` records what the CRM shows, not proof of contact: an outbound email, call, meeting held, note or sequence step you saw, or one the user stated. Never an inbound reply, never inferred from a completed task or a sent invite. When a CRM is connected in heyBTW, its own read stays the source for follow-up status.
* `attended_confirmed` sits beside the platform's attendance and never overwrites it. Only CHECKED\_IN on the platform is attended.
* Observations are append-only: a later one adds, never replaces.

## Errors and gaps

| Code | When | What to do |
| - | - | - |
| `invalid_parameter` | No `channel_id` or `event_name`; no `observations`; more than 20 items; a malformed item or `client_request_id`. Each call reports one parameter at a time. | Fix the named parameter. Nothing was recorded. |
| `entity_not_found` gap | Some references were rejected. Reasons include `not_found_in_workspace`, `invalid_email`, `met_by_not_found`, `invalid_note`, `no_fact`, `followed_up_at_no_offset`, `followed_up_at_in_future`, `invited_by_not_found`, `invited_list_id_not_found`, `nudged_message_ref_missing`. | Every other observation in the call was still recorded. Tell the user which were refused and why. |
| `entity_not_found` error | A partner's collab, the demo collab or an unknown id. | Only the workspace's own collabs take observations. |
| `insufficient_scope` | An OAuth token without `mcp:tools:write`. | Nothing was recorded. Reconnect with write access. |
| `rate_limit_exceeded` | 30 requests per 5 s, 60 per minute, 1000 per day per workspace. | Honor Retry-After. |
| `internal_error` | Transient. | Retry once with the same `client_request_id`. |

## Related

* [save\_task](https://heybtw.com/docs/tools/save-task.md): turn an accepted follow-up into a task, pointing at the `observation_id`.
* [get\_attendee\_touch\_status](https://heybtw.com/docs/tools/get-attendee-touch-status.md): recorded follow-ups appear there as `recorded_follow_ups`.
* [activate\_list](https://heybtw.com/docs/tools/activate-list.md): the path for List sends, which records its own invites.
* Playbooks: [The morning after](https://heybtw.com/docs/playbooks/morning-after.md), [Follow-up and ROI](https://heybtw.com/docs/playbooks/follow-up-and-roi.md).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.