Automation · Playbook 02

Retrieve a company's historic call transcripts.

Pull every past call transcript for a company on demand, without hunting through folders or relying on memory.

Diligence

Reference build

A working reference build that runs in production as a sub-zap — every other flow that needs "everything we've ever said with this company on a call" calls it with one input ( recordid) and gets back one string. Used by memo prep, partner briefs, follow-up drafting, founder coaching, and any agent that needs full call context for a company in one shot.

Vendors below are our choices. The flow is roles-not-vendors; every layer swaps cleanly.

Flow
01 · Entry
Sub-zap called with { recordid }
Any parent flow or agent invokes this — the contract is a single CRM company record_id in, one HTML transcript blob out
02 · Fetch — three nested paginated loops, all in one Code step
↻ paginated
02a · Meetings
GET /meetings?linked_object=companies&linked_record_id={recordid}
Cursor-walk every page (200/page). Throws on error — if the index is broken, fail loud.
02b · Recordings
For each meeting → GET /meetings/{id}/call_recordings
One meeting can have multiple recordings. Iterate them all. Per-meeting fetch failure = skip that meeting, not abort.
02c · Transcript pages
For each recording → paginate /transcript
Cursor-walk every page, concatenate, insert <br> before each [hh:mm:ss] speaker turn. Per-recording failure returns '' and continues.
03 · Sentinel guard
No meetings → 'No meetings' · meetings but no transcripts → 'No call transcripts available'
Caller can distinguish 'no history' from 'fetch failed' without inspecting types
04 · Compose output
Concatenate: '### {meeting title}<br>{transcript}<br><br>' per call
Chronological as the API returns (newest-first). HTML, not Markdown — survives LLM prompt concatenation cleanly.
05 · Return
{ calls: <single string> } back to the parent flow
One-field output keeps every caller's wiring trivial

Contract — in and out

The whole point of the sub-zap shape is that callers don't have to know anything about how the transcripts are stored. One field in, one field out.

FieldWhat it holds
recordidInput — CRM company record_id the caller wants the history for
meetings[]All meetings linked to that company (paginated, 200/page, no cap)
recordings[]Per meeting, every call_recording attached (multiple recordings per meeting are possible)
transcriptPer recording, the full paginated transcript with <br> before each [hh:mm:ss] speaker turn
callsOutput — concatenated HTML string: '### <Meeting title><br><transcript><br><br>' per call, in chronological order
sentinelOutput fallback — 'No meetings' or 'No call transcripts available' so the caller can branch deterministically

Gotchas

The things you only learn by running this in anger across a year of calls.

  • 01Single-purpose sub-zap: the whole thing is one Code step with three nested loops (meetings → recordings → transcript pages). Keeping it as a sub-zap means any parent flow — diligence memo, partner brief, founder coaching prep — can call it without duplicating the fetch logic.
  • 02Three layers of pagination, all required: meetings list (cursor), individual transcript pages (cursor). One missing cursor walk = silently truncated history. The function pages every level, no caps.
  • 03Recording-level loop, not meeting-level: one meeting can have multiple recordings (rejoined call, split recording, re-record). Iterating recordings under each meeting catches them all instead of grabbing only the first.
  • 04Sentinel returns instead of empty strings: the caller gets 'No meetings' or 'No call transcripts available' as the value, not '' — so a downstream LLM prompt or filter can distinguish 'company exists but no calls' from 'fetch failed'.
  • 05Per-page failure = skip, not throw: transcript fetch errors return '' for that recording so one bad recording doesn't kill the whole history pull. The meetings-list error DOES throw — if the index itself is broken, surface it.
  • 06HTML output, not Markdown: `<br>` and `###` because the most common consumer is an LLM prompt where '<br>' survives string concatenation cleanly and the model parses '###' as a section header without any rendering layer in between.
  • 07Chronological-as-API-returns: no resort. The meetings API returns newest-first; we keep that order. If a consumer needs oldest-first, they reverse — cheaper than sorting on the way out.

Swap matrix

Every layer is replaceable. The sub-zap shape and the in/out contract are what callers depend on.