curl --request POST \
--url https://app.octavehq.com/api/v2/transcript/search \
--header 'Content-Type: application/json' \
--header 'api_key: <api-key>' \
--data '
{
"query": "objections about pricing",
"companyDomain": "acme.com",
"entityOId": "<string>",
"speakerSide": "any",
"speakerEmail": "<string>",
"attributedPersonaOIds": [
"<string>"
],
"attributedSegmentOIds": [
"<string>"
],
"eventOIds": [
"<string>"
],
"contentFilter": {
"momentPhrases": [
"<string>"
],
"callPhrases": [
"<string>"
],
"excludeCallPhrases": [
"<string>"
]
},
"opportunityId": "<string>",
"callPurposes": [],
"expandQuery": false,
"minRelevance": 0.5,
"preferRecent": false,
"alpha": 0.5,
"sinceDate": "2026-04-01",
"untilDate": "<string>",
"includeInternalMeetings": false,
"limit": 8
}
'import requests
url = "https://app.octavehq.com/api/v2/transcript/search"
payload = {
"query": "objections about pricing",
"companyDomain": "acme.com",
"entityOId": "<string>",
"speakerSide": "any",
"speakerEmail": "<string>",
"attributedPersonaOIds": ["<string>"],
"attributedSegmentOIds": ["<string>"],
"eventOIds": ["<string>"],
"contentFilter": {
"momentPhrases": ["<string>"],
"callPhrases": ["<string>"],
"excludeCallPhrases": ["<string>"]
},
"opportunityId": "<string>",
"callPurposes": [],
"expandQuery": False,
"minRelevance": 0.5,
"preferRecent": False,
"alpha": 0.5,
"sinceDate": "2026-04-01",
"untilDate": "<string>",
"includeInternalMeetings": False,
"limit": 8
}
headers = {
"api_key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {api_key: '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
query: 'objections about pricing',
companyDomain: 'acme.com',
entityOId: '<string>',
speakerSide: 'any',
speakerEmail: '<string>',
attributedPersonaOIds: ['<string>'],
attributedSegmentOIds: ['<string>'],
eventOIds: ['<string>'],
contentFilter: {
momentPhrases: ['<string>'],
callPhrases: ['<string>'],
excludeCallPhrases: ['<string>']
},
opportunityId: '<string>',
callPurposes: [],
expandQuery: false,
minRelevance: 0.5,
preferRecent: false,
alpha: 0.5,
sinceDate: '2026-04-01',
untilDate: '<string>',
includeInternalMeetings: false,
limit: 8
})
};
fetch('https://app.octavehq.com/api/v2/transcript/search', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://app.octavehq.com/api/v2/transcript/search",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'query' => 'objections about pricing',
'companyDomain' => 'acme.com',
'entityOId' => '<string>',
'speakerSide' => 'any',
'speakerEmail' => '<string>',
'attributedPersonaOIds' => [
'<string>'
],
'attributedSegmentOIds' => [
'<string>'
],
'eventOIds' => [
'<string>'
],
'contentFilter' => [
'momentPhrases' => [
'<string>'
],
'callPhrases' => [
'<string>'
],
'excludeCallPhrases' => [
'<string>'
]
],
'opportunityId' => '<string>',
'callPurposes' => [
],
'expandQuery' => false,
'minRelevance' => 0.5,
'preferRecent' => false,
'alpha' => 0.5,
'sinceDate' => '2026-04-01',
'untilDate' => '<string>',
'includeInternalMeetings' => false,
'limit' => 8
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"api_key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://app.octavehq.com/api/v2/transcript/search"
payload := strings.NewReader("{\n \"query\": \"objections about pricing\",\n \"companyDomain\": \"acme.com\",\n \"entityOId\": \"<string>\",\n \"speakerSide\": \"any\",\n \"speakerEmail\": \"<string>\",\n \"attributedPersonaOIds\": [\n \"<string>\"\n ],\n \"attributedSegmentOIds\": [\n \"<string>\"\n ],\n \"eventOIds\": [\n \"<string>\"\n ],\n \"contentFilter\": {\n \"momentPhrases\": [\n \"<string>\"\n ],\n \"callPhrases\": [\n \"<string>\"\n ],\n \"excludeCallPhrases\": [\n \"<string>\"\n ]\n },\n \"opportunityId\": \"<string>\",\n \"callPurposes\": [],\n \"expandQuery\": false,\n \"minRelevance\": 0.5,\n \"preferRecent\": false,\n \"alpha\": 0.5,\n \"sinceDate\": \"2026-04-01\",\n \"untilDate\": \"<string>\",\n \"includeInternalMeetings\": false,\n \"limit\": 8\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("api_key", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://app.octavehq.com/api/v2/transcript/search")
.header("api_key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"query\": \"objections about pricing\",\n \"companyDomain\": \"acme.com\",\n \"entityOId\": \"<string>\",\n \"speakerSide\": \"any\",\n \"speakerEmail\": \"<string>\",\n \"attributedPersonaOIds\": [\n \"<string>\"\n ],\n \"attributedSegmentOIds\": [\n \"<string>\"\n ],\n \"eventOIds\": [\n \"<string>\"\n ],\n \"contentFilter\": {\n \"momentPhrases\": [\n \"<string>\"\n ],\n \"callPhrases\": [\n \"<string>\"\n ],\n \"excludeCallPhrases\": [\n \"<string>\"\n ]\n },\n \"opportunityId\": \"<string>\",\n \"callPurposes\": [],\n \"expandQuery\": false,\n \"minRelevance\": 0.5,\n \"preferRecent\": false,\n \"alpha\": 0.5,\n \"sinceDate\": \"2026-04-01\",\n \"untilDate\": \"<string>\",\n \"includeInternalMeetings\": false,\n \"limit\": 8\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://app.octavehq.com/api/v2/transcript/search")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["api_key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"query\": \"objections about pricing\",\n \"companyDomain\": \"acme.com\",\n \"entityOId\": \"<string>\",\n \"speakerSide\": \"any\",\n \"speakerEmail\": \"<string>\",\n \"attributedPersonaOIds\": [\n \"<string>\"\n ],\n \"attributedSegmentOIds\": [\n \"<string>\"\n ],\n \"eventOIds\": [\n \"<string>\"\n ],\n \"contentFilter\": {\n \"momentPhrases\": [\n \"<string>\"\n ],\n \"callPhrases\": [\n \"<string>\"\n ],\n \"excludeCallPhrases\": [\n \"<string>\"\n ]\n },\n \"opportunityId\": \"<string>\",\n \"callPurposes\": [],\n \"expandQuery\": false,\n \"minRelevance\": 0.5,\n \"preferRecent\": false,\n \"alpha\": 0.5,\n \"sinceDate\": \"2026-04-01\",\n \"untilDate\": \"<string>\",\n \"includeInternalMeetings\": false,\n \"limit\": 8\n}"
response = http.request(request)
puts response.read_body{
"_metadata": {
"requestId": "requestId",
"timestamp": "2021-01-01T00:00:00.000Z",
"usage": 0,
"message": "message"
},
"query": "<string>",
"calls": [
{
"eventOId": "<string>",
"opportunities": [
{
"oId": "<string>",
"name": "<string>",
"stageCategory": "<string>"
}
],
"moments": [
{
"quote": "<string>",
"speakerNames": [
"<string>"
],
"extractionTypes": [
"<string>"
],
"startSec": 123,
"score": 123
}
],
"callTitle": "<string>",
"companyName": "<string>",
"callDate": "<string>",
"callPurpose": "<string>",
"recordingUrl": "<string>"
}
],
"totalMoments": 123,
"latestCallAt": "<string>",
"indexedThrough": "<string>"
}{
"_metadata": {
"requestId": "requestId",
"timestamp": "2021-01-01T00:00:00.000Z",
"usage": 0,
"message": "message"
},
"message": "<string>"
}Search Call Transcripts
Semantic search over recorded call transcripts, returning verbatim, speaker-attributed moments with recording links and time anchors. Filter by company, library entity (competitor/objection/persona/…), speaker side (customer voice vs rep talk track), specific speaker or persona cohort (who SAID it), deal outcome (WON/LOST/OPEN, resolved live from CRM), a single call or opportunity, call sentiment, and date range. Calls are grouped with their linked CRM opportunities and current stage, so results can be read as won-deal or lost-deal precedents.
curl --request POST \
--url https://app.octavehq.com/api/v2/transcript/search \
--header 'Content-Type: application/json' \
--header 'api_key: <api-key>' \
--data '
{
"query": "objections about pricing",
"companyDomain": "acme.com",
"entityOId": "<string>",
"speakerSide": "any",
"speakerEmail": "<string>",
"attributedPersonaOIds": [
"<string>"
],
"attributedSegmentOIds": [
"<string>"
],
"eventOIds": [
"<string>"
],
"contentFilter": {
"momentPhrases": [
"<string>"
],
"callPhrases": [
"<string>"
],
"excludeCallPhrases": [
"<string>"
]
},
"opportunityId": "<string>",
"callPurposes": [],
"expandQuery": false,
"minRelevance": 0.5,
"preferRecent": false,
"alpha": 0.5,
"sinceDate": "2026-04-01",
"untilDate": "<string>",
"includeInternalMeetings": false,
"limit": 8
}
'import requests
url = "https://app.octavehq.com/api/v2/transcript/search"
payload = {
"query": "objections about pricing",
"companyDomain": "acme.com",
"entityOId": "<string>",
"speakerSide": "any",
"speakerEmail": "<string>",
"attributedPersonaOIds": ["<string>"],
"attributedSegmentOIds": ["<string>"],
"eventOIds": ["<string>"],
"contentFilter": {
"momentPhrases": ["<string>"],
"callPhrases": ["<string>"],
"excludeCallPhrases": ["<string>"]
},
"opportunityId": "<string>",
"callPurposes": [],
"expandQuery": False,
"minRelevance": 0.5,
"preferRecent": False,
"alpha": 0.5,
"sinceDate": "2026-04-01",
"untilDate": "<string>",
"includeInternalMeetings": False,
"limit": 8
}
headers = {
"api_key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {api_key: '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
query: 'objections about pricing',
companyDomain: 'acme.com',
entityOId: '<string>',
speakerSide: 'any',
speakerEmail: '<string>',
attributedPersonaOIds: ['<string>'],
attributedSegmentOIds: ['<string>'],
eventOIds: ['<string>'],
contentFilter: {
momentPhrases: ['<string>'],
callPhrases: ['<string>'],
excludeCallPhrases: ['<string>']
},
opportunityId: '<string>',
callPurposes: [],
expandQuery: false,
minRelevance: 0.5,
preferRecent: false,
alpha: 0.5,
sinceDate: '2026-04-01',
untilDate: '<string>',
includeInternalMeetings: false,
limit: 8
})
};
fetch('https://app.octavehq.com/api/v2/transcript/search', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://app.octavehq.com/api/v2/transcript/search",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'query' => 'objections about pricing',
'companyDomain' => 'acme.com',
'entityOId' => '<string>',
'speakerSide' => 'any',
'speakerEmail' => '<string>',
'attributedPersonaOIds' => [
'<string>'
],
'attributedSegmentOIds' => [
'<string>'
],
'eventOIds' => [
'<string>'
],
'contentFilter' => [
'momentPhrases' => [
'<string>'
],
'callPhrases' => [
'<string>'
],
'excludeCallPhrases' => [
'<string>'
]
],
'opportunityId' => '<string>',
'callPurposes' => [
],
'expandQuery' => false,
'minRelevance' => 0.5,
'preferRecent' => false,
'alpha' => 0.5,
'sinceDate' => '2026-04-01',
'untilDate' => '<string>',
'includeInternalMeetings' => false,
'limit' => 8
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"api_key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://app.octavehq.com/api/v2/transcript/search"
payload := strings.NewReader("{\n \"query\": \"objections about pricing\",\n \"companyDomain\": \"acme.com\",\n \"entityOId\": \"<string>\",\n \"speakerSide\": \"any\",\n \"speakerEmail\": \"<string>\",\n \"attributedPersonaOIds\": [\n \"<string>\"\n ],\n \"attributedSegmentOIds\": [\n \"<string>\"\n ],\n \"eventOIds\": [\n \"<string>\"\n ],\n \"contentFilter\": {\n \"momentPhrases\": [\n \"<string>\"\n ],\n \"callPhrases\": [\n \"<string>\"\n ],\n \"excludeCallPhrases\": [\n \"<string>\"\n ]\n },\n \"opportunityId\": \"<string>\",\n \"callPurposes\": [],\n \"expandQuery\": false,\n \"minRelevance\": 0.5,\n \"preferRecent\": false,\n \"alpha\": 0.5,\n \"sinceDate\": \"2026-04-01\",\n \"untilDate\": \"<string>\",\n \"includeInternalMeetings\": false,\n \"limit\": 8\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("api_key", "<api-key>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://app.octavehq.com/api/v2/transcript/search")
.header("api_key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"query\": \"objections about pricing\",\n \"companyDomain\": \"acme.com\",\n \"entityOId\": \"<string>\",\n \"speakerSide\": \"any\",\n \"speakerEmail\": \"<string>\",\n \"attributedPersonaOIds\": [\n \"<string>\"\n ],\n \"attributedSegmentOIds\": [\n \"<string>\"\n ],\n \"eventOIds\": [\n \"<string>\"\n ],\n \"contentFilter\": {\n \"momentPhrases\": [\n \"<string>\"\n ],\n \"callPhrases\": [\n \"<string>\"\n ],\n \"excludeCallPhrases\": [\n \"<string>\"\n ]\n },\n \"opportunityId\": \"<string>\",\n \"callPurposes\": [],\n \"expandQuery\": false,\n \"minRelevance\": 0.5,\n \"preferRecent\": false,\n \"alpha\": 0.5,\n \"sinceDate\": \"2026-04-01\",\n \"untilDate\": \"<string>\",\n \"includeInternalMeetings\": false,\n \"limit\": 8\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://app.octavehq.com/api/v2/transcript/search")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["api_key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"query\": \"objections about pricing\",\n \"companyDomain\": \"acme.com\",\n \"entityOId\": \"<string>\",\n \"speakerSide\": \"any\",\n \"speakerEmail\": \"<string>\",\n \"attributedPersonaOIds\": [\n \"<string>\"\n ],\n \"attributedSegmentOIds\": [\n \"<string>\"\n ],\n \"eventOIds\": [\n \"<string>\"\n ],\n \"contentFilter\": {\n \"momentPhrases\": [\n \"<string>\"\n ],\n \"callPhrases\": [\n \"<string>\"\n ],\n \"excludeCallPhrases\": [\n \"<string>\"\n ]\n },\n \"opportunityId\": \"<string>\",\n \"callPurposes\": [],\n \"expandQuery\": false,\n \"minRelevance\": 0.5,\n \"preferRecent\": false,\n \"alpha\": 0.5,\n \"sinceDate\": \"2026-04-01\",\n \"untilDate\": \"<string>\",\n \"includeInternalMeetings\": false,\n \"limit\": 8\n}"
response = http.request(request)
puts response.read_body{
"_metadata": {
"requestId": "requestId",
"timestamp": "2021-01-01T00:00:00.000Z",
"usage": 0,
"message": "message"
},
"query": "<string>",
"calls": [
{
"eventOId": "<string>",
"opportunities": [
{
"oId": "<string>",
"name": "<string>",
"stageCategory": "<string>"
}
],
"moments": [
{
"quote": "<string>",
"speakerNames": [
"<string>"
],
"extractionTypes": [
"<string>"
],
"startSec": 123,
"score": 123
}
],
"callTitle": "<string>",
"companyName": "<string>",
"callDate": "<string>",
"callPurpose": "<string>",
"recordingUrl": "<string>"
}
],
"totalMoments": 123,
"latestCallAt": "<string>",
"indexedThrough": "<string>"
}{
"_metadata": {
"requestId": "requestId",
"timestamp": "2021-01-01T00:00:00.000Z",
"usage": 0,
"message": "message"
},
"message": "<string>"
}Authorizations
Body
Transcript search query and filters
What to find in call transcripts, e.g. 'objections about pricing' or 'how prospects describe onboarding pain'
1"objections about pricing"
Restrict to calls with one company by its website domain (e.g. 'acme.com')
"acme.com"
Restrict to moments linked to a library entity (competitor, objection, persona, etc.) by its oId
'external' finds customer/prospect voice, 'internal' finds rep talk tracks
external, internal, any Only moments where this person spoke (contact email)
Only moments spoken BY contacts classified into any of these personas, e.g. 'objections raised by CTOs or CISOs'. Different from entityOId, which finds moments ABOUT an entity
Only calls with companies classified into any of these segments — quotes from within a market segment, resolved live from company classifications
Restrict to specific calls (Event oIds) — chain from /event/list results
Exact-phrase content criteria. Within each list ANY phrase matches (OR — list synonyms); across fields all conditions apply (AND)
Show child attributes
Show child attributes
Restrict to calls linked to one CRM opportunity/deal. Accepts either an Octave oId (crmo_*) or the CRM's native id (Salesforce Id / HubSpot object id / Attio record UUID, e.g. pasted from a CRM URL) — pass whichever you have
Restrict to calls on deals with this outcome (resolved live from CRM), e.g. 'objection quotes on deals we lost'
WON, LOST, OPEN Only calls with this overall pipeline-extracted sentiment
POSITIVE, NEUTRAL, NEGATIVE, UNKNOWN Only calls classified with any of these purposes (OR). E.g. ['ESCALATION_CHURN_RISK'] for save-attempt voice, ['DISCOVERY','DEMO'] for early-funnel calls
DISCOVERY, DEMO, TECHNICAL_EVALUATION, WORKING_SESSION, NEGOTIATION_PRICING, ONBOARDING, CHECK_IN_SUCCESS, QBR_RENEWAL, EXPANSION_UPSELL, ESCALATION_CHURN_RISK, SUPPORT, INTERNAL_SYNC, OTHER Set true when the query is a short or abstract topic (a bare entity name, 'pricing pushback') — it gets rewritten into a hypothetical transcript passage before matching, improving recall. Leave false when the query already reads like something a speaker would say verbatim. Adds ~1s latency
Relative relevance floor (0-1): drop moments scoring below this fraction of the BEST match's score. Use ~0.5-0.7 when fewer, higher-confidence quotes beat volume; omit for exploratory searches
0 <= x <= 1Decay scores by call age (90-day half-life) so newer calls win ties. Use for 'current objections', 'latest messaging', trend questions; leave false for all-time or historical evidence. For a hard cutoff use sinceDate instead
Semantic-vs-keyword weight (default 0.7). Lower toward 0.3 when exact words must appear verbatim (product names, jargon, quoted phrases); raise toward 0.9 for conceptual/paraphrase matching where wording will differ
0 <= x <= 1Only calls on or after this ISO date (e.g. 2026-04-01)
"2026-04-01"
Only calls on or before this ISO date (a date-only value is inclusive of the whole day it names)
Also search calls with no external participant (internal meetings)
Maximum number of moments to return (default: 8, max: 25)
1 <= x <= 258
Response
Matching calls with their moments
Show child attributes
Show child attributes
Matching calls with their moments
Show child attributes
Show child attributes
Timestamp of the workspace's most recent processed call
Timestamp of the newest call present in the transcript index. When this is older than latestCallAt (or null), the newest call(s) are not searchable yet — results may miss them.