> For clean Markdown of any page, append .md to the page URL. For a complete documentation index, see https://docs.sonicalpha.ai/llms.txt. For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.sonicalpha.ai/_mcp/server. For SDK generation or contract validation, use the authoritative OpenAPI spec at https://api.sonicalpha.ai/openapi.json — this docs domain's /openapi.json is a regenerated copy that can differ in auth modeling and strictness annotations.

# Search insights

POST https://api.sonicalpha.ai/v1/insights/search
Content-Type: application/json

Reference: https://docs.sonicalpha.ai/api-reference/insights/search-insights

## Authentication

- `Authorization` header (bearer token, required) — Bearer authentication of the form `Bearer <token>`, where token is your auth token.

## Request

### Body (application/json)

This endpoint expects an InsightSearchRequest.

- `query` (string, optional, nullable) — Natural-language query, up to 512 characters. Results are ranked by relevance to it. Whitespace-only is treated as absent. For long research questions, use `/v1/insights/ask`.
- `tickers` (list of string, optional, nullable) — Filter to insights mentioning at least one of these tickers. OMIT to include untickered insights too.
- `types` (list of enum, optional, nullable) — Filter by claim type: `thesis` = a forward-looking argument, `observation` = a qualitative read on the present, `data_point` = a concrete number. Multiple values are ORed (any-of). Omit for all three.
  - Allowed values: `thesis`, `observation`, `data_point`
- `mentions` (list of MentionFilter, optional, nullable) — Company-specific sentiment pairs. A hit matches when at least one requested pair holds on a SINGLE mention — "bearish about NVDA", not "mentions NVDA and is bearish about something". Multiple pairs are ORed (any-of), like every other list filter.
- `speaker_roles` (list of enum, optional, nullable) — Filter by what the speaker does professionally: `operator` runs a business, `investor` deploys capital, `researcher` produces research, `commentator` is a journalist or host, `government` is a public official. `unknown` when the profession can't be determined. Multiple values are ORed (any-of).
  - Allowed values: `operator`, `investor`, `researcher`, `commentator`, `government`, `unknown`
- `date_from` (string, optional, nullable) — Earliest `published_at` to include, inclusive. Accepts a bare date (`2026-07-18`) or a full timestamp.
- `date_to` (string, optional, nullable) — Latest `published_at` to include, inclusive — a bare date covers the whole day.
- `ids` (list of string, optional, nullable) — Fetch specific insights by `id` (format `in-...`) — e.g. refreshing previously saved results. Ids that no longer resolve are omitted — compare the result count to the ids you sent.
- `episode_ids` (list of string, optional, nullable) — Scope the search to specific episodes — `episode_id` values.
- `podcast_ids` (list of string, optional, nullable) — Scope the search to specific shows — `id` values from the podcast directory. "Everything this show has said about X" is one query.
- `rank_by` (enum, optional, nullable) — `relevance` (the default when `query` is set) or `recency` (newest first — the default when `query` is absent).
  - Allowed values: `relevance`, `recency`
- `top_k` (integer, optional, default: 25) — Results per page, up to 25. Further results are reachable via `next_cursor`.
- `cursor` (string, optional, nullable) — Opaque pagination token from a previous response's `next_cursor`. Send it ALONE — the original query's parameters are replayed server-side, and combining `cursor` with any other field is a 400. Cursors expire after ~15 minutes.
- `include_quotes` (boolean, optional, default: false) — Attach the verbatim transcript passage to each hit as `quote`. A hit whose transcript is unavailable carries `quote: null` and the search succeeds regardless.

## Response

### 200

Successful Response

- `results` (list of InsightHit, required) — Ranked insights, most-relevant first — newest first when no `query` is set.
- `took_ms` (integer, required) — Server-side time for this request, in milliseconds.
- `next_cursor` (string, required, nullable) — Pass back as `cursor` to fetch the next page. `null` on the last page — read `pagination_end` for why paging stopped.
- `pagination_end` (enum, required, nullable) — Why paging stopped — null while more pages exist. `end_of_results`: every match was returned. `ranking_exhausted`: the relevance ranking reached its ranked window (~150 results; ~500 with a `mentions` filter) — narrow the query for more depth.
  - Allowed values: `ranking_exhausted`, `end_of_results`

## Errors

### 400 Bad Request Error

Malformed request: unknown field, bad cursor, or out-of-range parameter

- `error` (ErrorResponseError, required) — The error envelope. Every non-2xx response on every endpoint uses this shape.

### 401 Unauthorized Error

Missing or invalid API key

- `error` (ErrorResponseError, required) — The error envelope. Every non-2xx response on every endpoint uses this shape.

### 422 Unprocessable Entity Error

Invalid request (validation)

- `error` (ErrorResponseError, required) — The error envelope. Every non-2xx response on every endpoint uses this shape.

### 429 Too Many Requests Error

Rate limit or daily quota exceeded; includes Retry-After

- `error` (ErrorResponseError, required) — The error envelope. Every non-2xx response on every endpoint uses this shape.

### 500 Internal Server Error

Internal error

- `error` (ErrorResponseError, required) — The error envelope. Every non-2xx response on every endpoint uses this shape.

## Types

### MentionFilter

One requested (ticker, sentiment) pair for the `mentions` filter. A hit matches when at least one pair holds on a SINGLE mention — "bearish about NVDA", not "mentions NVDA and is bearish about something". `sentiment` optional: a bare ticker pair is just a ticker match.

- `ticker` (string, required) — Canonical symbol to match, e.g. `NVDA`.
- `sentiment` (enum, optional, nullable) — Stance to require on that ticker. Omit to match the ticker at any sentiment.
  - Allowed values: `bullish`, `bearish`, `neutral`

### InsightHit

- `id` (string, optional, nullable) — Stable insight id, format `in-...`. Resolvable via `GET /v1/insights/{insight_id}` and accepted by the `ids` search filter.
- `speaker_name` (string, optional, nullable) — Who made the claim. `null` when the speaker could not be identified.
- `speaker_title` (string, optional, nullable) — The speaker's role and organization as stated on the appearance — e.g. `CEO, Ritholtz Wealth Management`.
- `mentions` (list of Mention, optional, nullable) — Companies this claim is about, each carrying its own sentiment. One insight can mention several companies and be bullish on one, bearish on another.
- `published_at` (string, optional, nullable) — When the episode aired — ISO 8601 UTC. This is the point-in-time anchor for the claim, not when we indexed it.
- `podcast_name` (string, optional, nullable) — Display name of the show.
- `episode_title` (string, optional, nullable) — Title of the episode the claim came from.
- `episode_audio_url` (string, optional, nullable) — Full episode audio. Combine with `start_seconds` to play the claim itself.
- `episode_image_url` (string, optional, nullable) — Episode artwork.
- `podcast_image_url` (string, optional, nullable) — Show artwork.
- `episode_url` (string, optional, nullable) — Public web page for the episode.
- `episode_id` (string, optional, nullable) — Id of the source episode. Resolvable via `GET /v1/episodes/{episode_id}` and accepted by the `episode_ids` search filter.
- `podcast_id` (string, optional, nullable) — Id of the source show. Resolvable via `GET /v1/podcasts/{podcast_id}` and accepted by the `podcast_ids` search filter.
- `quote` (string, optional, nullable) — Verbatim transcript passage behind `body`. Populated only when `include_quotes` is true, and `null` when the transcript is unavailable.
- `title` (string, optional) — One-line headline of the extracted claim.
- `body` (string, optional) — The extracted claim — our one-paragraph paraphrase. `quote` is the verbatim transcript.
- `type` (enum, optional) — thesis = forward-looking argument, observation = qualitative read on the present, data_point = a concrete number.
  - Allowed values: `thesis`, `observation`, `data_point`
- `speaker_role` (enum, optional) — What the speaker does professionally: `operator` runs a business, `investor` deploys capital, `researcher` produces research, `commentator` is a journalist or host, `government` is a public official. `unknown` when it can't be determined; the field is always present.
  - Allowed values: `operator`, `investor`, `researcher`, `commentator`, `government`, `unknown`
- `start_seconds` (double, optional) — Position of the claim in the episode audio, seconds from the start.
- `end_seconds` (double, optional) — End of the claim in the episode audio, seconds from the start.

### ErrorResponseError

The error envelope. Every non-2xx response on every endpoint uses this shape.

- `type` (enum, required)
  - Allowed values: `invalid_request_error`, `authentication_error`, `permission_error`, `not_found_error`, `rate_limit_error`, `api_error`
- `message` (string, required)

### Mention

One company mention on an insight. `ticker` is the canonical symbol, or null for companies we don't track canonically; `company` always carries the extracted display name.

- `company` (string, optional, nullable) — Extracted display name of the company — always present, including for private and untracked names.
- `ticker` (string, optional, nullable) — Canonical symbol, or `null` for companies we don't track canonically. Untickered names are still reachable through semantic queries.
- `sentiment` (enum, optional, nullable) — The speaker's stance on this company in this claim: `bullish`, `bearish`, or `neutral`.
  - Allowed values: `bullish`, `bearish`, `neutral`

## Examples

**Request**

```json
{
  "query": "datacenter power constraints",
  "mentions": [
    {
      "ticker": "NVDA",
      "sentiment": "bearish"
    }
  ],
  "speaker_roles": [
    "investor"
  ],
  "top_k": 25
}
```

**Response**

```json
{
  "results": [
    {
      "id": "string",
      "speaker_name": "string",
      "speaker_title": "string",
      "mentions": [
        {
          "company": "string",
          "ticker": "string",
          "sentiment": "bullish"
        }
      ],
      "published_at": "string",
      "podcast_name": "string",
      "episode_title": "string",
      "episode_audio_url": "string",
      "episode_image_url": "string",
      "podcast_image_url": "string",
      "episode_url": "string",
      "episode_id": "string",
      "podcast_id": "string",
      "quote": "string",
      "title": "string",
      "body": "string",
      "type": "thesis",
      "speaker_role": "operator",
      "start_seconds": 1.1,
      "end_seconds": 1.1
    }
  ],
  "took_ms": 1,
  "next_cursor": "string",
  "pagination_end": "ranking_exhausted"
}
```

**SDK Code**

```python insights_search-insights_example
import requests

url = "https://api.sonicalpha.ai/v1/insights/search"

payload = {
    "query": "datacenter power constraints",
    "mentions": [
        {
            "ticker": "NVDA",
            "sentiment": "bearish"
        }
    ],
    "speaker_roles": ["investor"],
    "top_k": 25
}
headers = {
    "Authorization": "Bearer <token>",
    "Content-Type": "application/json"
}

response = requests.post(url, json=payload, headers=headers)

print(response.json())
```

```javascript insights_search-insights_example
const url = 'https://api.sonicalpha.ai/v1/insights/search';
const options = {
  method: 'POST',
  headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
  body: '{"query":"datacenter power constraints","mentions":[{"ticker":"NVDA","sentiment":"bearish"}],"speaker_roles":["investor"],"top_k":25}'
};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go insights_search-insights_example
package main

import (
	"fmt"
	"strings"
	"net/http"
	"io"
)

func main() {

	url := "https://api.sonicalpha.ai/v1/insights/search"

	payload := strings.NewReader("{\n  \"query\": \"datacenter power constraints\",\n  \"mentions\": [\n    {\n      \"ticker\": \"NVDA\",\n      \"sentiment\": \"bearish\"\n    }\n  ],\n  \"speaker_roles\": [\n    \"investor\"\n  ],\n  \"top_k\": 25\n}")

	req, _ := http.NewRequest("POST", url, payload)

	req.Header.Add("Authorization", "Bearer <token>")
	req.Header.Add("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby insights_search-insights_example
require 'uri'
require 'net/http'

url = URI("https://api.sonicalpha.ai/v1/insights/search")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n  \"query\": \"datacenter power constraints\",\n  \"mentions\": [\n    {\n      \"ticker\": \"NVDA\",\n      \"sentiment\": \"bearish\"\n    }\n  ],\n  \"speaker_roles\": [\n    \"investor\"\n  ],\n  \"top_k\": 25\n}"

response = http.request(request)
puts response.read_body
```

```java insights_search-insights_example
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.post("https://api.sonicalpha.ai/v1/insights/search")
  .header("Authorization", "Bearer <token>")
  .header("Content-Type", "application/json")
  .body("{\n  \"query\": \"datacenter power constraints\",\n  \"mentions\": [\n    {\n      \"ticker\": \"NVDA\",\n      \"sentiment\": \"bearish\"\n    }\n  ],\n  \"speaker_roles\": [\n    \"investor\"\n  ],\n  \"top_k\": 25\n}")
  .asString();
```

```php insights_search-insights_example
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://api.sonicalpha.ai/v1/insights/search', [
  'body' => '{
  "query": "datacenter power constraints",
  "mentions": [
    {
      "ticker": "NVDA",
      "sentiment": "bearish"
    }
  ],
  "speaker_roles": [
    "investor"
  ],
  "top_k": 25
}',
  'headers' => [
    'Authorization' => 'Bearer <token>',
    'Content-Type' => 'application/json',
  ],
]);

echo $response->getBody();
```

```csharp insights_search-insights_example
using RestSharp;

var client = new RestClient("https://api.sonicalpha.ai/v1/insights/search");
var request = new RestRequest(Method.POST);
request.AddHeader("Authorization", "Bearer <token>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{\n  \"query\": \"datacenter power constraints\",\n  \"mentions\": [\n    {\n      \"ticker\": \"NVDA\",\n      \"sentiment\": \"bearish\"\n    }\n  ],\n  \"speaker_roles\": [\n    \"investor\"\n  ],\n  \"top_k\": 25\n}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

```swift insights_search-insights_example
import Foundation

let headers = [
  "Authorization": "Bearer <token>",
  "Content-Type": "application/json"
]
let parameters = [
  "query": "datacenter power constraints",
  "mentions": [
    [
      "ticker": "NVDA",
      "sentiment": "bearish"
    ]
  ],
  "speaker_roles": ["investor"],
  "top_k": 25
] as [String : Any]

let postData = JSONSerialization.data(withJSONObject: parameters, options: [])

let request = NSMutableURLRequest(url: NSURL(string: "https://api.sonicalpha.ai/v1/insights/search")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "POST"
request.allHTTPHeaderFields = headers
request.httpBody = postData as Data

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```