curl --request POST \
--url https://production-api.joinrings.com/v1/notes/search \
--header 'Content-Type: application/json' \
--header 'x-api-key: <api-key>' \
--data '
{
"query": "<string>",
"active_opportunities_only": false,
"end_date": "<string>",
"entity_uuid": "<string>",
"limit": 10,
"opportunity_uuids": [
"<string>"
],
"start_date": "<string>"
}
'import requests
url = "https://production-api.joinrings.com/v1/notes/search"
payload = {
"query": "<string>",
"active_opportunities_only": False,
"end_date": "<string>",
"entity_uuid": "<string>",
"limit": 10,
"opportunity_uuids": ["<string>"],
"start_date": "<string>"
}
headers = {
"x-api-key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'x-api-key': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
query: '<string>',
active_opportunities_only: false,
end_date: '<string>',
entity_uuid: '<string>',
limit: 10,
opportunity_uuids: ['<string>'],
start_date: '<string>'
})
};
fetch('https://production-api.joinrings.com/v1/notes/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://production-api.joinrings.com/v1/notes/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' => '<string>',
'active_opportunities_only' => false,
'end_date' => '<string>',
'entity_uuid' => '<string>',
'limit' => 10,
'opportunity_uuids' => [
'<string>'
],
'start_date' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"x-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://production-api.joinrings.com/v1/notes/search"
payload := strings.NewReader("{\n \"query\": \"<string>\",\n \"active_opportunities_only\": false,\n \"end_date\": \"<string>\",\n \"entity_uuid\": \"<string>\",\n \"limit\": 10,\n \"opportunity_uuids\": [\n \"<string>\"\n ],\n \"start_date\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("x-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://production-api.joinrings.com/v1/notes/search")
.header("x-api-key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"query\": \"<string>\",\n \"active_opportunities_only\": false,\n \"end_date\": \"<string>\",\n \"entity_uuid\": \"<string>\",\n \"limit\": 10,\n \"opportunity_uuids\": [\n \"<string>\"\n ],\n \"start_date\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://production-api.joinrings.com/v1/notes/search")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["x-api-key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"query\": \"<string>\",\n \"active_opportunities_only\": false,\n \"end_date\": \"<string>\",\n \"entity_uuid\": \"<string>\",\n \"limit\": 10,\n \"opportunity_uuids\": [\n \"<string>\"\n ],\n \"start_date\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"query": "<string>",
"results": [
{
"note_uuid": "<string>",
"score": 123,
"snippets": [
{
"content": "<string>",
"score": 123
}
],
"title": "<string>",
"associations": [
{
"entity_type": "<string>",
"entity_uuid": "<string>",
"uuid": "<string>",
"context": "<string>"
}
],
"created_at": 123
}
],
"total": 123
}Search notes
Matches notes by meaning as well as keywords and returns results grouped
by source note with matching text snippets. search_mode=comprehensive
adds a deeper relevance pass that also weighs each note’s associated
person/company context (not just raw note text) for broad, ambiguous
asks — e.g. “give me everyone I’ve researched who fits this profile” — at
the cost of several seconds of extra latency. Every other mode stays fast
for direct/precise lookups.
Scope to a specific entity/opportunity with entity_uuid+entity_type, or
across many opportunities at once with opportunity_uuids/
active_opportunities_only — e.g. “data-room concerns across all active
opportunities this quarter” resolves as one call scoped to open
opportunities, no per-note follow-up needed. Each result’s associations
field carries its linked entities/opportunities inline for client-side
grouping.
curl --request POST \
--url https://production-api.joinrings.com/v1/notes/search \
--header 'Content-Type: application/json' \
--header 'x-api-key: <api-key>' \
--data '
{
"query": "<string>",
"active_opportunities_only": false,
"end_date": "<string>",
"entity_uuid": "<string>",
"limit": 10,
"opportunity_uuids": [
"<string>"
],
"start_date": "<string>"
}
'import requests
url = "https://production-api.joinrings.com/v1/notes/search"
payload = {
"query": "<string>",
"active_opportunities_only": False,
"end_date": "<string>",
"entity_uuid": "<string>",
"limit": 10,
"opportunity_uuids": ["<string>"],
"start_date": "<string>"
}
headers = {
"x-api-key": "<api-key>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {'x-api-key': '<api-key>', 'Content-Type': 'application/json'},
body: JSON.stringify({
query: '<string>',
active_opportunities_only: false,
end_date: '<string>',
entity_uuid: '<string>',
limit: 10,
opportunity_uuids: ['<string>'],
start_date: '<string>'
})
};
fetch('https://production-api.joinrings.com/v1/notes/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://production-api.joinrings.com/v1/notes/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' => '<string>',
'active_opportunities_only' => false,
'end_date' => '<string>',
'entity_uuid' => '<string>',
'limit' => 10,
'opportunity_uuids' => [
'<string>'
],
'start_date' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Content-Type: application/json",
"x-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://production-api.joinrings.com/v1/notes/search"
payload := strings.NewReader("{\n \"query\": \"<string>\",\n \"active_opportunities_only\": false,\n \"end_date\": \"<string>\",\n \"entity_uuid\": \"<string>\",\n \"limit\": 10,\n \"opportunity_uuids\": [\n \"<string>\"\n ],\n \"start_date\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("x-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://production-api.joinrings.com/v1/notes/search")
.header("x-api-key", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"query\": \"<string>\",\n \"active_opportunities_only\": false,\n \"end_date\": \"<string>\",\n \"entity_uuid\": \"<string>\",\n \"limit\": 10,\n \"opportunity_uuids\": [\n \"<string>\"\n ],\n \"start_date\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://production-api.joinrings.com/v1/notes/search")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["x-api-key"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"query\": \"<string>\",\n \"active_opportunities_only\": false,\n \"end_date\": \"<string>\",\n \"entity_uuid\": \"<string>\",\n \"limit\": 10,\n \"opportunity_uuids\": [\n \"<string>\"\n ],\n \"start_date\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"query": "<string>",
"results": [
{
"note_uuid": "<string>",
"score": 123,
"snippets": [
{
"content": "<string>",
"score": 123
}
],
"title": "<string>",
"associations": [
{
"entity_type": "<string>",
"entity_uuid": "<string>",
"uuid": "<string>",
"context": "<string>"
}
],
"created_at": 123
}
],
"total": 123
}Authorizations
Body
Search query text.
Restrict to notes associated with an open/active-stage opportunity. Combines with opportunity_uuids (intersection) when both are given, or applies tenant-wide when omitted.
ISO datetime upper bound on note creation date.
Type of entity_uuid (PERSON/COMPANY/OPPORTUNITY). Must be paired with entity_uuid.
PERSON, COMPANY, OPPORTUNITY Scope results to notes associated with this single entity. Must be paired with entity_type.
Max number of notes to return (1-200).
Scope results to notes associated with any of these opportunities — use for asks spanning multiple opportunities at once, e.g. 'data-room concerns across all active opportunities this quarter'. At most 200 UUIDs.
Controls how results are ranked. Defaults to 'recency' when omitted. Choose based on the user's intent:
- 'recency': favours recent notes with a time-decay score boost. Use for: 'latest notes', 'recent conversations', 'what happened last week'.
- 'historical': favours older notes. Use for: 'first meeting', 'earliest notes', 'how did we start with X'.
- 'comprehensive': considers more candidates AND ranks them against each note's associated person/company context (job title, company description, mentions), not just raw note text — use for broad/ambiguous asks like 'give me everyone I've researched who fits this profile' or 'all notes mentioning Y'. Slower (several seconds) than every other mode, so prefer this only for genuinely broad asks, not direct/precise lookups.
- 'balanced': distributes results evenly across time. Use when a representative sample across full history is needed.
- 'relevance': pure semantic match, no time factor. Use when recency is explicitly irrelevant.
recency, historical, relevance, comprehensive, balanced ISO datetime lower bound on note creation date.