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.
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.
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.
| Field | What it holds |
|---|---|
| recordid | Input — 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) |
| transcript | Per recording, the full paginated transcript with <br> before each [hh:mm:ss] speaker turn |
| calls | Output — concatenated HTML string: '### <Meeting title><br><transcript><br><br>' per call, in chronological order |
| sentinel | Output 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.