Sales calls
Turn sales and renewal calls into signals linked to the moment they were said: what is extracted, what is kept, and the three ways a transcript gets in.
A sales or renewal call is the most direct feedback a company gets, and the least read. Sentriment keeps each transcript, checks that the call is a sales or customer conversation, and turns what the customer said into signals: each one a short title and the passage it was said in, linked to that moment of the call. Every signal is an ordinary feedback item, so calls join the same themes as tickets, reviews and in-app feedback, and a customer who says "we are evaluating alternatives" on a renewal call turns up in User Health like any other at-risk signal.
Three ways in: connect Granola directly, upload a transcript on the Sources page, or send one through POST /api/v1/transcripts from any notetaker or a Zapier step.
What is extracted
#One reading of the transcript yields every signal in the call, usually five to twenty on a sales call. Each is the passage the customer said it in, complete sentences in their own words and language, under a one-line title that says what it is about. Each is one of nine kinds, and each carries a strength: explicit (said outright), implied (the customer only confirmed the rep's paraphrase) or blocking (stated as a condition of buying or staying).
| Parameter | In | Description |
|---|---|---|
objection | kind | A reason the customer gives for not buying, or not buying yet. |
feature_request | kind | Something the customer asks the product to do that it does not do today. |
pain | kind | A problem the customer describes with their current setup or with the product. |
praise | kind | Something the customer says works well or that they value. |
competitor_mention | kind | A named alternative the customer is using, evaluating or comparing against. |
pricing_reaction | kind | How the customer reacts to a price, a plan or the pricing model. |
churn_risk | kind | A sign that an existing customer is considering leaving or reducing use. |
expansion_signal | kind | A sign that the customer wants more seats, more usage or another team on board. |
question | kind | A question the customer asked that the rep had to answer, often a gap in the product or its docs. |
Each signal also gets the existing feedback type (bug, request, praise, complaint, question), sentiment, emotions and language, so the Requests and Bugs views, Reports and Ask work on calls with no new filters to learn.
What is kept, and redaction
#The transcript is kept, for your workspace's retention period like everything else, so you can read any call in full with each signal marked where it was said. Also kept per call: the source id and a link back to the call in the source tool, the title, when it happened, how long it was, the word count, who was on it, the rep's email, the folder and the language. The notetaker's own summary and notes are never fetched.
Personal data is redacted by default. Before a call is stored or analysed, the names of the people on the invite and anything that looks like an email address, a phone number, a card or an account number are replaced, and attendees are kept by email only. The Granola connection has a Redact personal data switch: turn it off and calls are stored and analysed as said, with names, so every signal shows who said it. Uploads and API transcripts are always redacted.
Retention and erasure reach transcripts like every other record: past the retention period a call's words, attendees and title are deleted, and an erasure request for a person deletes them from every call that person attended, spoke on or was attributed.
How a call becomes items
#- 1
Stored, redacted unless you chose otherwise
The transcript is stored with the redaction described above applied first, so no model and no page ever sees what redaction removed.
- 2
Is it a sales or customer conversation?
A fast model reads the call against what your company does (Settings, What the company does; every project has its own, so a company with two businesses describes both or gives each a project) and decides whether it is worth analysing: a sales or customer conversation, or anything where someone outside your company talks about what you offer. An internal meeting, a job interview or an investor update is listed as not analysed, with the reason, and is not counted. Analyse anyway on the call overrides it.
- 3
One extraction, whole passages
A single model call reads the whole transcript and returns every signal in it. The item text is the passage the customer said it in, complete sentences in their language, with filler words and obvious transcription slips cleaned up and nothing paraphrased. Only the customer's side is a signal; the rep's lines are context.
- 4
Linked back to the moment it was said
Each passage is matched against the stored transcript in code, character by character, and linked to the stretch it came from when at least 80% of its characters match. The signal links there from the Feed, and the call page highlights it. A passage that matches nowhere is kept and marked "not found in the transcript".
- 5
The title that clusters, then the pipeline
Each signal's one-line title is a plain-English restatement ("exports time out above 50k rows") in the same register as the aspects extracted from tickets and reviews, so a call and a ticket about the same issue land in the same theme. Each signal is then a feedback item with source
calland goes through embedding, clustering and health scoring like everything else. The external attendee is resolved by email: an existing customer's profile picks up the signals; an unknown email becomes a prospect (see below).
How calls are counted
#Calls are counted as items by length: one item per 500 words of transcript, both speakers included, with a minimum of 2 per call. A 45-minute call runs to about 6,750 spoken words, so it counts as about 14 items. A call is counted once, when it is analysed: after it was judged a sales or customer conversation and before the extraction runs.
A call that is not a sales or customer conversation (skip reason not_relevant) is not counted, and neither is a call the filters stop before any reading (too short, too long, no customer speech, no external attendee). An analysed call counts even when nothing is found in it. Sending the same external_id twice never counts twice; the second send returns the first row unchanged, and analysing a call again is never counted again.
Uploaded and backfilled calls are history: they draw from the one-time 1,000-item import allowance first, shared with CSV imports, and then from the plan's monthly volume. If the plan has no room, the call is recorded as plan_blocked and analysed when it does.
Items and signals are different counts
Billing surfaces count items by length ("counted as 14 items"). Content surfaces such as the Feed and theme pages count signals, the passages actually found. A quiet call can count as 14 items and produce 3 signals.
Upload a transcript
#Under Sources → Sales calls, choose a file. Three formats are read:
| Parameter | In | Description |
|---|---|---|
.txt / .md | file | One speaker per line. Me: and Them: labels, or names (Alice Smith: …, Speaker 1: …), with an optional timestamp such as [00:14:32] at the start of the line. A line without a label continues the previous one. |
.vtt | file | WebVTT captions as exported by most meeting tools. <v Name> voice tags become speaker labels; cues from the same speaker under two seconds apart are merged. |
.csv | file | A spreadsheet export of calls, one per row, with a transcript column (Granola's own export included). Columns are found by header keyword: transcript, title, date, attendees. Up to 500 calls and 2 MB per file. |
[00:00:12] Me: Thanks for making the time. What prompted the call?
[00:00:20] Them: Exports. Anything above 50k rows times out and the team
exports every Monday.
[00:01:04] Me: Understood. Is that the only blocker?
[00:01:09] Them: Pricing per seat is hard to justify for the read-only users.Speaker mapping is the same for every format: me and them are literal; any other label that matches your name, your first name or the local part of your email is you; every other label is the customer. A file with no speaker labels at all is recorded as skipped, reason no_quotable_speech: nothing is guessed.
Granola on any plan: the CSV export
Granola exports your own notes as a CSV from Settings → Profile → Generate CSV. It covers the notes you own, once per 24 hours, and the file uploads here as is. This is the day-one path for Granola users on plans without API access.
Uploads are history (see below). A file that cannot be read is refused whole with the reason and what was found, for example the column names of a CSV with no transcript column. Nothing is sliced or partially imported.
Send transcripts through the API
#POST /api/v1/transcripts takes one call per request and answers 202 before any model runs. It accepts secret keys only, 60 requests per minute per key, bodies up to 2 MB. The full field list, every response and every error are on the Transcripts API page; the shape is:
| Parameter | In | Description |
|---|---|---|
external_idrequired | body | Your stable id for the call, such as the notetaker's meeting id. Sending the same id again returns the existing row and never counts twice. |
segments or textrequired | body | Exactly one. segments is an array of { speaker, text, start_sec? } with speaker "me", "them" or the label as written. text is the whole transcript in the line format above, or WebVTT with format: "vtt". |
owner_email, owner_name | body | The rep who ran the notetaker, and how that person is labelled in the transcript. Both are used to map named speakers to "me"; owner_email also sets your own domain for the attendee filter. |
attendees | body | Up to 50 { email?, name?, external? }. At least one attendee must be external (an email outside owner_email's domain, or external: true) or the call is recorded as skipped. Names are used for redaction and then dropped. |
title, started_at, duration_sec, url, language, folder | body | Provenance. started_at decides live versus history; url is the link back to the call in the source tool. |
curl -X POST https://app.sentriment.com/api/v1/transcripts \
-H "Authorization: Bearer sk_live_…" \
-H "Content-Type: application/json" \
-d '{
"external_id": "note_8f3a2c",
"title": "Acme discovery call",
"started_at": "2026-09-12T14:00:00Z",
"duration_sec": 2700,
"url": "https://notes.example-notetaker.com/n/8f3a2c",
"owner_email": "sam@yourcompany.example",
"owner_name": "Sam Okafor",
"attendees": [{ "email": "jane@acme.example", "name": "Jane Doe" }],
"text": "Them: We have been evaluating alternatives because exports time out above 50k rows.\nMe: Understood. How big is a typical export?\nThem: About 80k rows, every Monday."
}'{
"id": "01KX8VPHE0XN0JCDM6GSRC5HDQ",
"status": "queued",
"metered_items": 0,
"word_count": 2740
}status is queued when the call will be read, or skipped with a skip_reason when a filter stopped it (under 800 words, over about three hours, no customer speech, no external attendee). metered_items is 0 here: a call is counted when it is analysed. Analysis runs asynchronously; poll GET /api/v1/transcripts/{id} for the status, the count and the signals, including not_relevant when the call turned out not to be a sales or customer conversation, and plan_blocked when the plan had no room.
Zapier recipe for any notetaker
Trigger: Note added to Granola folder (or the equivalent "transcript ready" trigger of your notetaker). Action: Webhooks by Zapier → POST. URL https://app.sentriment.com/api/v1/transcripts, payload type JSON, header Authorization: Bearer sk_…. Map external_id to the note id, title to the note title, started_at to the note date, url to the note link, owner_email to your own email, and text to the transcript field. Send attendees when the trigger exposes them; without an external attendee the call is skipped.
The Granola connector
#Connect Granola once and every call it finishes arrives on its own. This is the only notetaker Sentriment connects to natively; everything else comes through the upload door or the API above.
What you need on Granola's side
API access, which Granola includes on its Business and Enterprise plans. Create the key under Settings → API: either a personal key, which reads your own notes and the ones shared with you, or a workspace key created by an admin, which reads what the workspace can see. One setup detail decides whether this works at all: the sales calls have to be in a shared space or folder the key can see. Granola does not let an admin export a colleague's private notes, so a workspace key cannot read calls that live only in one person's private space. On a plan without API access, use the CSV export or the API below, both cover every Granola plan.
- 1
Connect with the key and your company domains
Under Sources → Granola, paste the key, list your company domains (the part after the @ in your work email, one or several) and leave Redact personal data on unless you want names kept. That list is what tells a colleague from a customer, so it is confirmed by you rather than guessed, and a public mail provider is refused with its name: if gmail.com counted as yours, every prospect on Gmail would look like a colleague and their calls would be skipped as internal.
Sentriment then reads the metadata of 20 recent meetings and tells you what the filters would do before anything is analysed: "Of 20 recent meetings, 7 would be analysed · 11 internal · 2 under 10 minutes". Only attendees, length and folder are read for this, no transcript is fetched, and nothing is counted.
Connecting is forward-only, like every connector: from that moment on, new calls flow in. Nothing from before it is touched until you import history yourself.
- 2
New calls arrive on their own
Granola tells Sentriment when a call's transcript is ready, so a call is usually analysed within minutes of ending. That notification carries the note id and nothing else; Sentriment then reads that one call and stores it, redacted unless you turned redaction off.
A 15-minute check runs behind it and picks up anything the notification missed, a delivery that failed, or a call shared into the folder after the fact. It is also why the connector still works when the notification cannot be set up at all. The card says which of the two is carrying your calls, and how long ago each last ran.
- 3
Import past calls from a list you confirm
History is a deliberate step, and for calls it shows you the actual list rather than a count, a call is worth many items, so a single number is not something anyone can reasonably agree to. Pick a date and Sentriment reads the metadata of the calls since then (about 20 seconds for 100 meetings) and shows one row each: date, title, length, the customer's domain, and an estimated item count from the call's length. Untick any call you do not want. Calls the filters would skip are listed underneath with their reasons and their own checkboxes, so a customer call that looks internal can be pulled in.
Under the list: the totals, what your import allowance covers, and roughly how long it will take. Confirm, and it runs in the background, the card counts calls as they are analysed, and the exact item count replaces the estimate as each transcript is read and its words are known. Imported calls are history, not news (see below). Running an import again from the same date is safe: calls already analysed are left out of the list entirely, so the second run offers the next ones instead.
- 4
Read a call again if you need to
Re-analyse on a finished call reads it again from Granola and analyses it fresh, so a change to the redaction switch or to what your company does applies to it. Re-analyse all calls does the same for every analysed call, newest first. Either replaces the call's existing signals rather than adding to them, and is never counted again. An uploaded or API call is analysed again from the stored transcript.
Which calls are read is up to you, under Filters on the connector row. All four are decided from a call's metadata before any transcript is fetched or anything is counted:
| Parameter | In | Description |
|---|---|---|
Your company domains | filter | A call needs at least one attendee whose email domain is not yours. An all-internal meeting is skipped, with the reason shown. |
Minimum length | filter | Default 10 minutes: below that a call is a check-in, not a conversation. A call whose calendar entry has no length passes here and is judged by its word count instead. |
Title rules | filter | Case-insensitive fragments. "1:1" keeps your one-to-ones out without naming anyone. |
Folders | filter | Optional. Choose one or more Granola folders and only calls in them are read, the simplest way to say "sales calls only". Left empty, every folder the key can see is included. |
Every skipped call is still recorded with its reason, so the card can tell you "Skipped 14 calls in the last 7 days: 12 internal, 2 under 800 words" rather than leaving you to wonder where a call went. A skipped call is never counted. Change the filters whenever you like and check them against 20 recent meetings again; changes apply to new calls, not to calls already read.
Live calls and history
#A call is live when it reaches Sentriment within 48 hours of started_at (a missing started_at counts as now). Everything else is history: uploads, backfills and API calls older than 48 hours. History joins themes, counts in totals and scores health, but never fires an alert, never registers as a spike and never reopens a resolved theme. A quarter of old calls sent in one go changes what you know, not what pages you.
Prospects and customers
#Who was on the call decides what a signal can do, not where it came from. If the external attendee's email matches a customer you have identified, it is a customer call: the signals land on that profile, count towards the customer value behind a theme, and a churn-risk signal on a renewal call feeds the at-risk rule exactly as a ticket would. If the email is unknown, the person becomes a prospect: scored and visible in themes today, with the profile identified by email. Three things are still to come: the prospect badge in Users, the exclusion of prospects from health aggregates, and a theme's pipeline figure from a deal_value trait passed through identify. The pipeline figure will be its own setting, shown beside customer value and never summed with it; deal_value cannot be chosen as the customer-value trait.
Recording consent
#Recording consent is the customer's responsibility: the party running the notetaker obtains it, under the rules that apply where the call takes place. Granola provides in-meeting notices for this. Sentriment receives a transcript that already exists and keeps it as described above, for the workspace's retention period.