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

# Ask a research question

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

Ask a research question in natural language.

SonicAlpha agentically searches the corpus — it decomposes the
question into sub-queries, runs them, and curates the most relevant
evidence into a ranked `sources` set. Set `"answer": true` to also
synthesize that evidence into a written, cited answer.

The agent picks its filters from the question's wording — there is
nothing else to configure:

- **Name a show or episode** ("How have the All-In podcast's views
  on AI capex evolved over the last 6 months?") and retrieval scopes
  to that source.
- **Name an audience** ("What do *operators* say about data center
  power constraints?") and it filters by speaker role.
- **Give a time window** ("since June", "over the last 3 months")
  and it dates the search.

By default the response is evidence-only — `sources` with
`answer: null`, in ~10-30s — the natural mode when you're combining
the evidence with other data in your own generation. With
`"answer": true`, synthesis adds ~15-25s, and every `[n]` citation
in the answer maps to a source object.

If no sources are returned, `note_type` says why.
`"source_not_covered"`: the question named a show or episode that
isn't in our corpus — we can't know what it said. `"no_evidence"`:
nothing in the corpus addressed the question. When sources are
returned, `note_type` is null.

For live progress and answer tokens over SSE, use
`POST /v1/insights/ask/stream`. To run retrieval yourself with
explicit filters, use `POST /v1/insights/search`.

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

## 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 InsightAskRequest.

- `query` (string, required) — The research question, in natural language (8–1000 characters) — phrase it as a question or claim, not keywords. Scope by naming shows, audiences, or time windows directly in the wording.
- `answer` (boolean, optional, default: false) — Default (false) returns the agent-curated evidence (`sources`) with `answer: null` in ~10-30s. Set true for a written, cited answer — every `[n]` maps to a source object; adds ~15-25s.
- `include_quotes` (boolean, optional, default: false) — Attach the verbatim transcript passage to each source as `quote`.

## Response

### 200

Successful Response

- `query` (string, required) — The question as submitted, echoed back.
- `answer` (string, required, nullable) — Markdown synthesis with [n] citations. Null in evidence-only mode (the default — sources still returned) and when no evidence was found.
- `sources` (list of AskSource, required) — The agent-curated evidence set, best-first.
- `took_ms` (integer, required) — Server-side time for this request, in milliseconds. Ask runs several searches plus an optional synthesis, so expect seconds rather than milliseconds.
- `note_type` (enum, required, nullable) — Null when evidence was found. source_not_covered = a named show/episode is not in the corpus; no_evidence = covered ground, nothing matched the question.
  - Allowed values: `source_not_covered`, `no_evidence`
- `note` (string, required, nullable) — Display-ready sentence paired with note_type — for source_not_covered it names the missing show or episode. Null when evidence was found.

## 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

### AskSource

A search-hit insight object (same fields as /v1/insights/search results) plus `n` — the number the answer's [n] citations resolve to.

- `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.
- `n` (integer, optional) — Citation number; the answer's [n] markers resolve to this source.

### 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": "What are investors expecting from NVDA's datacenter revenue over the next two quarters?",
  "answer": true
}
```

**Response**

```json
{
  "query": "string",
  "answer": "string",
  "sources": [
    {
      "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,
      "n": 1
    }
  ],
  "took_ms": 1,
  "note_type": "source_not_covered",
  "note": "string"
}
```

**SDK Code**

```python insights_ask_example
import requests

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

payload = {
    "query": "What are investors expecting from NVDA's datacenter revenue over the next two quarters?",
    "answer": True
}
headers = {
    "Authorization": "Bearer <token>",
    "Content-Type": "application/json"
}

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

print(response.json())
```

```javascript insights_ask_example
const url = 'https://api.sonicalpha.ai/v1/insights/ask';
const options = {
  method: 'POST',
  headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
  body: '{"query":"What are investors expecting from NVDA\'s datacenter revenue over the next two quarters?","answer":true}'
};

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

```go insights_ask_example
package main

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

func main() {

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

	payload := strings.NewReader("{\n  \"query\": \"What are investors expecting from NVDA's datacenter revenue over the next two quarters?\",\n  \"answer\": true\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_ask_example
require 'uri'
require 'net/http'

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

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\": \"What are investors expecting from NVDA's datacenter revenue over the next two quarters?\",\n  \"answer\": true\n}"

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

```java insights_ask_example
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.post("https://api.sonicalpha.ai/v1/insights/ask")
  .header("Authorization", "Bearer <token>")
  .header("Content-Type", "application/json")
  .body("{\n  \"query\": \"What are investors expecting from NVDA's datacenter revenue over the next two quarters?\",\n  \"answer\": true\n}")
  .asString();
```

```php insights_ask_example
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('POST', 'https://api.sonicalpha.ai/v1/insights/ask', [
  'body' => '{
  "query": "What are investors expecting from NVDA\'s datacenter revenue over the next two quarters?",
  "answer": true
}',
  'headers' => [
    'Authorization' => 'Bearer <token>',
    'Content-Type' => 'application/json',
  ],
]);

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

```csharp insights_ask_example
using RestSharp;

var client = new RestClient("https://api.sonicalpha.ai/v1/insights/ask");
var request = new RestRequest(Method.POST);
request.AddHeader("Authorization", "Bearer <token>");
request.AddHeader("Content-Type", "application/json");
request.AddParameter("application/json", "{\n  \"query\": \"What are investors expecting from NVDA's datacenter revenue over the next two quarters?\",\n  \"answer\": true\n}", ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
```

```swift insights_ask_example
import Foundation

let headers = [
  "Authorization": "Bearer <token>",
  "Content-Type": "application/json"
]
let parameters = [
  "query": "What are investors expecting from NVDA's datacenter revenue over the next two quarters?",
  "answer": true
] as [String : Any]

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

let request = NSMutableURLRequest(url: NSURL(string: "https://api.sonicalpha.ai/v1/insights/ask")! 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()
```