Sales calls

Turn sales and renewal calls into verbatim signals: what is extracted, what is never stored, 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 reads each transcript once, keeps only what the customer said in their own words, and drops the rest. Every quote becomes 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 up to 30 signals. 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).

ParameterInDescription
objectionkindA reason the customer gives for not buying, or not buying yet.
feature_requestkindSomething the customer asks the product to do that it does not do today.
painkindA problem the customer describes with their current setup or with the product.
praisekindSomething the customer says works well or that they value.
competitor_mentionkindA named alternative the customer is using, evaluating or comparing against.
pricing_reactionkindHow the customer reacts to a price, a plan or the pricing model.
churn_riskkindA sign that an existing customer is considering leaving or reducing use.
expansion_signalkindA sign that the customer wants more seats, more usage or another team on board.
questionkindA 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 never stored

#

The transcript is read once and never kept. Not stored, anywhere: the transcript body, raw or redacted; the notetaker's summary and notes; the rep's own words (a rep saying "our export is fast" is not feedback); and anyone's name, on either side of the call. An attendee is identified by email only, so erasure by email covers calls the same way it covers everything else.

What is kept per call: the source id and a link back to the call in the source tool, the title with personal names removed, when it happened, how long it was, the word count, the external attendee's email domain, the rep's email, the folder and the language. What is kept per signal: the customer's quote, redacted before storage, plus a short redacted context window of the sentence before and after it, the offset into the call, the kind and the strength.

Sealed while in flight

Between receipt and extraction the redacted segments are held encrypted, and they are deleted in the same transaction that writes the signals. Whatever happens, the copy is gone within 72 hours. Re-analysing a call after a prompt change means sending it again.

How a call becomes items

#
  1. 1

    Names out, then the regex pass

    Every attendee name from the calendar is removed from the segments before anything is stored or sent to a model, then the same PII redaction that covers every other source runs. The transcript reaches the model already redacted.

  2. 2

    One extraction, verbatim quotes

    A single model call reads the whole transcript and returns up to 30 signals. The item text is the customer's quote as spoken: original language, at most 60 words. Only the customer's side is quotable; the rep's lines are context, never items.

  3. 3

    The restatement that clusters

    Each quote gets a one-line restatement in plain English ("exports time out above 50k rows"). That restatement is what gets embedded and clustered, 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. The quote stays the item; the restatement is what clusters it.

  4. 4

    Verification against the source

    Every quote is matched against the transcript in code. If the model's version does not match the source closely enough, the source sentence replaces it and the substitution is counted on the call. A restatement carrying a link, an email or anything not literally in the transcript is refused. Nothing the model invents survives.

  5. 5

    Into the pipeline

    Each signal is inserted as a feedback item with source call and 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. The count is fixed at the door, before the model runs, from the word count in the response.

A call counts even when nothing is found in it, because the reading is the cost. That includes a call the model reads and then judges internal (skip reason internal_call). A call the door's own filters stop before any reading (too short, too long, no customer speech, no external attendee) is recorded with its reason and not counted. Sending the same external_id twice never counts twice; the second send returns the first row unchanged.

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 quotes 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:

ParameterInDescription
.txt / .mdfileOne 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.
.vttfileWebVTT 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.
.csvfileA 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.
acme-discovery.txt
[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:

ParameterInDescription
external_idrequiredbodyYour 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 textrequiredbodyExactly 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_namebodyThe 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.
attendeesbodyUp 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, folderbodyProvenance. started_at decides live versus history; url is the link back to the call in the source tool.
Request
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."
  }'
202 Accepted
{
  "id": "01KX8VPHE0XN0JCDM6GSRC5HDQ",
  "status": "queued",
  "metered_items": 6,
  "word_count": 2740
}

status is queued when the call will be analysed, skipped with a skip_reason when a filter stopped it (under 800 words, over about three hours, no customer speech, no external attendee), or plan_blocked. Extraction runs asynchronously; poll GET /api/v1/transcripts/{id} for the signal count, and for internal_call when the model found it was not a customer call after all.

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. 1

    Connect with the key and your company domains

    Under Sources → Granola, paste the key and list your company domains, the part after the @ in your work email, one or several. 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. 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, keeps the quotes and keeps no copy of the transcript.

    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. 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. 4

    Read a call again if you need to

    Re-analyse on a finished call fetches it from Granola again and reads it fresh, useful after you change the filters. It replaces that call's existing signals rather than adding to them, and it is never counted again. This works only because Granola still holds the transcript and Sentriment does not, which is also why re-analysing an uploaded call means uploading the file again.

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:

ParameterInDescription
Your company domainsfilterA call needs at least one attendee whose email domain is not yours. An all-internal meeting is skipped, with the reason shown.
Minimum lengthfilterDefault 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 rulesfilterCase-insensitive fragments. "1:1" keeps your one-to-ones out without naming anyone.
FoldersfilterOptional. 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 revenue at stake, 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 separate pipeline figure from a deal_value trait passed through identify, shown beside revenue at stake and never added to it.

Was this page helpful?