Q&As
Read and write the Q&A backlog on a deal — the questions the sales team has asked (or been asked) and the current state of each answer.
Two directions
- Discovery — the sales team hasn't yet learned about a 8P field. A Discovery Q&A points at a specific 8P schema field (e.g.,
profile.tenure) that's blank or under-supported, so the seller knows what to ask next. - RFI (Request For Information) — the buyer asked a question the seller couldn't answer in the moment. Attached to a contact.
Three states
- Open — new / unanswered.
- Answered — has an answer attached. Transitions automatically when an answer with confidence ≥ 0.6 is attached.
- Dismissed — won't-answer. Not a hard delete — preserves audit trail.
Scopes
| Operation | Scope required |
|---|---|
Read (GET) | qna:read |
| Create / answer / transition | qna:write |
Writes require Idempotency-Key.
Endpoints on this page
| Method | Endpoint | Description |
|---|---|---|
GET | /api/v1alpha1/deals/{dealId}/qnas | Fetch the bundled Q&A backlog for a deal — 2 directions × 3 states = 6 buckets. Details ↓ |
GET | /api/v1alpha1/deals/{dealId}/qnas/meetings/{meetingId} | Filter to Q&As arising from one specific meeting. Details ↓ |
GET | /api/v1alpha1/qnas/{qnaId} | Fetch one Q&A item. Details ↓ |
POST | /api/v1alpha1/deals/{dealId}/qnas | Create a Q&A item (Discovery or RFI). Details ↓ |
POST | /api/v1alpha1/qnas/{qnaId}/answer | Attach an answer. Auto-transitions to Answered on high confidence. Details ↓ |
POST | /api/v1alpha1/qnas/{qnaId}/transitions/{targetState} | Explicit state transition. Details ↓ |
Get bundled backlog
GET /api/v1alpha1/deals/{dealId}/qnas
Fetch the full Q&A backlog, bucketed by direction and state. Matches the dashboard's Live View pane exactly.
Response
- Six buckets — 2 directions × 3 states. Every bucket is always present.
countis the full server-side count.items[]is capped at 25 items per bucket in v1alpha1. Partners rendering "47 open items" show thecountand let users drill into the list viaGET /api/v1alpha1/qnas/{id}per item.
Each item follows the QnaItemmodel shape:
linkedInsight— present on Discovery Q&As. Points at the 8P field being asked about.contact— present on RFI Q&As.{ id, displayName }reference to the contact who asked.occurrences[]— every time this Q&A was raised. The same question can arise in multiple meetings; each occurrence is captured.occurrences[].sourceenum:cortex-agent | user | webhook.
Errors
404 deal-not-found— id unknown or cross-org
curl -s "https://altus.cirrusinsight.com/api/v1alpha1/deals/deal_01H8YKQ2N9RXVT/qnas" \
-H "X-Cirrus-Api-Key: $TOKEN"import requests
deal_id = "deal_01H8YKQ2N9RXVT"
response = requests.get(
f"https://altus.cirrusinsight.com/api/v1alpha1/deals/{deal_id}/qnas",
headers={"X-Cirrus-Api-Key": token},
)
response.raise_for_status()
backlog = response.json()using System.Net.Http;
using System.Net.Http.Headers;
var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-Cirrus-Api-Key", token);
var dealId = "deal_01H8YKQ2N9RXVT";
var response = await client.GetAsync(
$"https://altus.cirrusinsight.com/api/v1alpha1/deals/{dealId}/qnas");
response.EnsureSuccessStatusCode();
var backlog = await response.Content.ReadAsStringAsync();{
"dealId": "deal_...",
"discovery": {
"open": { "count": 3, "items": [ /* QnaItem shape */ ] },
"answered": { "count": 8, "items": [ /* ... */ ] },
"dismissed": { "count": 1, "items": [ /* ... */ ] }
},
"rfi": {
"open": { "count": 2, "items": [ /* ... */ ] },
"answered": { "count": 5, "items": [ /* ... */ ] },
"dismissed": { "count": 0, "items": [ /* ... */ ] }
}
}{
"id": "qna_...",
"direction": "discovery",
"state": "open",
"questionText": "What's the buyer's role tenure?",
"answerText": null,
"importance": "high",
"linkedInsight": {
"schemaType": "profile",
"fieldKey": "tenure"
},
"contact": null,
"occurrences": [
{
"meetingId": "mtg_...",
"occurredAt": "2026-06-16T17:03:22Z",
"source": "cortex-agent"
}
],
"createdAt": "2026-06-16T17:03:22Z",
"updatedAt": "2026-06-16T17:03:22Z"
}Get meeting slice
GET /api/v1alpha1/deals/{dealId}/qnas/meetings/{meetingId}
Filter the deal's Q&A backlog to items that arose from one specific meeting.
Useful when rendering a meeting-detail surface with "questions from this meeting" alongside the transcript.
Response
Same 6-bucket shape as GET /qnas, each items[] filtered to Q&As whose occurrences[] includes the given meetingId. Counts reflect the filtered set.
Errors
404 deal-not-found404 meeting-not-found
Meeting doesn't need to be linked to the deal. Passing an unrelated (dealId, meetingId) returns empty buckets — the collection is scoped to the deal; the meeting filter is just a filter.
curl -s "https://altus.cirrusinsight.com/api/v1alpha1/deals/deal_01H8YKQ2N9RXVT/qnas/meetings/mtg_01H9ABCDEF" \
-H "X-Cirrus-Api-Key: $TOKEN"import requests
deal_id = "deal_01H8YKQ2N9RXVT"
meeting_id = "mtg_01H9ABCDEF"
response = requests.get(
f"https://altus.cirrusinsight.com/api/v1alpha1/deals/{deal_id}/qnas/meetings/{meeting_id}",
headers={"X-Cirrus-Api-Key": token},
)
response.raise_for_status()
slice_ = response.json()using System.Net.Http;
using System.Net.Http.Headers;
var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-Cirrus-Api-Key", token);
var dealId = "deal_01H8YKQ2N9RXVT";
var meetingId = "mtg_01H9ABCDEF";
var response = await client.GetAsync(
$"https://altus.cirrusinsight.com/api/v1alpha1/deals/{dealId}/qnas/meetings/{meetingId}");
response.EnsureSuccessStatusCode();
var slice = await response.Content.ReadAsStringAsync();Get one Q&A
GET /api/v1alpha1/qnas/{qnaId}
Fetch one Q&A item by its qna_... id.
Response
QnaItem shape as documented above.
Errors
404 qna-item-not-found— id unknown or cross-org
curl -s "https://altus.cirrusinsight.com/api/v1alpha1/qnas/qna_01H8YKR3P4STUV" \
-H "X-Cirrus-Api-Key: $TOKEN"import requests
qna_id = "qna_01H8YKR3P4STUV"
response = requests.get(
f"https://altus.cirrusinsight.com/api/v1alpha1/qnas/{qna_id}",
headers={"X-Cirrus-Api-Key": token},
)
response.raise_for_status()
qna = response.json()using System.Net.Http;
using System.Net.Http.Headers;
var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-Cirrus-Api-Key", token);
var qnaId = "qna_01H8YKR3P4STUV";
var response = await client.GetAsync(
$"https://altus.cirrusinsight.com/api/v1alpha1/qnas/{qnaId}");
response.EnsureSuccessStatusCode();
var qna = await response.Content.ReadAsStringAsync();Create a Q&A
POST /api/v1alpha1/deals/{dealId}/qnas
Create a Q&A. Two flavours based on direction:
Headers
| Header | Description |
|---|---|
Idempotency-Key | Required. |
Request body — Discovery
Request body — RFI
| Field | Required | Notes |
|---|---|---|
direction | yes | discovery or rfi. |
questionText | yes | |
importance | no | Default medium. |
linkedInsight | Discovery only | .schemaType + .fieldKey (must exist in the 8P schema). |
contactId | RFI only | Must belong to the calling org. |
occurrences | yes | At least one entry. meetingId + occurredAt per entry. |
Response
201 Created with the newly-created QnaItem.
Errors
400 idempotency-key-required404 deal-not-found404 contact-not-found— RFIcontactIdunknown or cross-org404 meeting-not-found— occurrencemeetingIdunknown or cross-org422 qna-shape-invalid— missinglinkedInsighton Discovery, or missingcontactIdon RFI422 insight-field-invalid—linkedInsight.fieldKeyisn't in the schema
curl -s -X POST "https://altus.cirrusinsight.com/api/v1alpha1/deals/deal_01H8YKQ2N9RXVT/qnas" \
-H "X-Cirrus-Api-Key: $TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"direction": "discovery",
"questionText": "What'\''s the buyer'\''s role tenure?",
"importance": "high",
"linkedInsight": { "schemaType": "profile", "fieldKey": "tenure" },
"occurrences": [
{ "meetingId": "mtg_01H9ABCDEF", "occurredAt": "2026-06-16T17:03:22Z" }
]
}'import requests
import uuid
deal_id = "deal_01H8YKQ2N9RXVT"
headers = {
"X-Cirrus-Api-Key": token,
"Idempotency-Key": str(uuid.uuid4()),
}
body = {
"direction": "discovery",
"questionText": "What's the buyer's role tenure?",
"importance": "high",
"linkedInsight": {"schemaType": "profile", "fieldKey": "tenure"},
"occurrences": [
{"meetingId": "mtg_01H9ABCDEF", "occurredAt": "2026-06-16T17:03:22Z"}
],
}
response = requests.post(
f"https://altus.cirrusinsight.com/api/v1alpha1/deals/{deal_id}/qnas",
headers=headers,
json=body,
)
response.raise_for_status()
qna = response.json()using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-Cirrus-Api-Key", token);
var dealId = "deal_01H8YKQ2N9RXVT";
var body = new
{
direction = "discovery",
questionText = "What's the buyer's role tenure?",
importance = "high",
linkedInsight = new { schemaType = "profile", fieldKey = "tenure" },
occurrences = new[]
{
new { meetingId = "mtg_01H9ABCDEF", occurredAt = "2026-06-16T17:03:22Z" }
}
};
var request = new HttpRequestMessage(
HttpMethod.Post,
$"https://altus.cirrusinsight.com/api/v1alpha1/deals/{dealId}/qnas")
{
Content = new StringContent(
JsonSerializer.Serialize(body),
Encoding.UTF8,
"application/json")
};
request.Headers.Add("Idempotency-Key", Guid.NewGuid().ToString());
var response = await client.SendAsync(request);
response.EnsureSuccessStatusCode();
var qna = await response.Content.ReadAsStringAsync();{
"direction": "discovery",
"questionText": "What's the buyer's role tenure?",
"importance": "high",
"linkedInsight": {
"schemaType": "profile",
"fieldKey": "tenure"
},
"occurrences": [
{ "meetingId": "mtg_...", "occurredAt": "2026-06-16T17:03:22Z" }
]
}{
"direction": "rfi",
"questionText": "Does Cirrus support SSO with Okta?",
"importance": "high",
"contactId": "cont_...",
"occurrences": [
{ "meetingId": "mtg_...", "occurredAt": "2026-06-16T17:14:00Z" }
]
}Attach an answer
POST /api/v1alpha1/qnas/{qnaId}/answer
Attach an answer. Auto-transitions the item to Answered when the answer's confidence ≥ 0.6. Otherwise the item stays Open with an answer draft attached.
Headers
| Header | Description |
|---|---|
Idempotency-Key | Required. |
Request body
| Field | Required | Notes |
|---|---|---|
answerText | yes | |
source.type | yes | meeting / manual / grounding-doc / external. |
source.meetingId | conditional | Required when type: "meeting". chunkId optional. |
source.documentId | conditional | Required when type: "grounding-doc". |
confidence | no | 0.0–1.0. Default: 1.0 for manual, 0.5 for grounding-doc / external, Cortex's own score for meeting-sourced. |
Response
200 OK with the updated QnaItem. If confidence triggered auto-transition, state will be answered.
Errors
400 idempotency-key-required404 qna-item-not-found409 qna-transition-invalid— item isdismissed. Dismissed items can't be answered without an explicit transition-back-to-open first.422 answer-source-invalid— missingmeetingIdontype: "meeting", ordocumentIdontype: "grounding-doc"
curl -s -X POST "https://altus.cirrusinsight.com/api/v1alpha1/qnas/qna_01H8YKR3P4STUV/answer" \
-H "X-Cirrus-Api-Key: $TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"answerText": "Sample Guest has been at Acme for 3 years, per LinkedIn.",
"source": {
"type": "meeting",
"meetingId": "mtg_01H9ABCDEF",
"chunkId": "chnk_01H9XYZ123"
},
"confidence": 0.85
}'import requests
import uuid
qna_id = "qna_01H8YKR3P4STUV"
headers = {
"X-Cirrus-Api-Key": token,
"Idempotency-Key": str(uuid.uuid4()),
}
body = {
"answerText": "Sample Guest has been at Acme for 3 years, per LinkedIn.",
"source": {
"type": "meeting",
"meetingId": "mtg_01H9ABCDEF",
"chunkId": "chnk_01H9XYZ123",
},
"confidence": 0.85,
}
response = requests.post(
f"https://altus.cirrusinsight.com/api/v1alpha1/qnas/{qna_id}/answer",
headers=headers,
json=body,
)
response.raise_for_status()
qna = response.json()using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-Cirrus-Api-Key", token);
var qnaId = "qna_01H8YKR3P4STUV";
var body = new
{
answerText = "Sample Guest has been at Acme for 3 years, per LinkedIn.",
source = new
{
type = "meeting",
meetingId = "mtg_01H9ABCDEF",
chunkId = "chnk_01H9XYZ123"
},
confidence = 0.85
};
var request = new HttpRequestMessage(
HttpMethod.Post,
$"https://altus.cirrusinsight.com/api/v1alpha1/qnas/{qnaId}/answer")
{
Content = new StringContent(
JsonSerializer.Serialize(body),
Encoding.UTF8,
"application/json")
};
request.Headers.Add("Idempotency-Key", Guid.NewGuid().ToString());
var response = await client.SendAsync(request);
response.EnsureSuccessStatusCode();
var qna = await response.Content.ReadAsStringAsync();{
"answerText": "Sample Guest has been at Acme for 3 years, per LinkedIn.",
"source": {
"type": "meeting",
"meetingId": "mtg_...",
"chunkId": "chnk_..."
},
"confidence": 0.85
}Transition state
POST /api/v1alpha1/qnas/{qnaId}/transitions/{targetState}
Explicit state transition. Use when the answer-attach endpoint doesn't fit — dismissing without answering, reopening a dismissed item, forcing a transition regardless of confidence.
targetState is open, answered, or dismissed.
Headers
| Header | Description |
|---|---|
Idempotency-Key | Required. |
Request body (optional)
Response
200 OK with the updated QnaItem.
Allowed transitions
| From | → allowed target states |
|---|---|
open | answered, dismissed |
answered | open, dismissed |
dismissed | open |
Errors
400 idempotency-key-required400 invalid-target-state—targetStatenot one of the three404 qna-item-not-found409 qna-transition-invalid— transition not allowed from current state. Response body includescurrentStateso racing callers can recover:
curl -s -X POST "https://altus.cirrusinsight.com/api/v1alpha1/qnas/qna_01H8YKR3P4STUV/transitions/dismissed" \
-H "X-Cirrus-Api-Key: $TOKEN" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"reason": "Answered externally; no longer relevant to Cirrus"
}'import requests
import uuid
qna_id = "qna_01H8YKR3P4STUV"
target_state = "dismissed"
headers = {
"X-Cirrus-Api-Key": token,
"Idempotency-Key": str(uuid.uuid4()),
}
body = {"reason": "Answered externally; no longer relevant to Cirrus"}
response = requests.post(
f"https://altus.cirrusinsight.com/api/v1alpha1/qnas/{qna_id}/transitions/{target_state}",
headers=headers,
json=body,
)
response.raise_for_status()
qna = response.json()using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Text.Json;
var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-Cirrus-Api-Key", token);
var qnaId = "qna_01H8YKR3P4STUV";
var targetState = "dismissed";
var body = new
{
reason = "Answered externally; no longer relevant to Cirrus"
};
var request = new HttpRequestMessage(
HttpMethod.Post,
$"https://altus.cirrusinsight.com/api/v1alpha1/qnas/{qnaId}/transitions/{targetState}")
{
Content = new StringContent(
JsonSerializer.Serialize(body),
Encoding.UTF8,
"application/json")
};
request.Headers.Add("Idempotency-Key", Guid.NewGuid().ToString());
var response = await client.SendAsync(request);
response.EnsureSuccessStatusCode();
var qna = await response.Content.ReadAsStringAsync();{
"reason": "Answered externally; no longer relevant to Cirrus"
}{
"type": "https://docs.cirrusinsight.com/platform-api/errors/qna-transition-invalid",
"title": "Q&A transition not allowed",
"status": 409,
"detail": "Cannot transition to 'dismissed' from current state 'dismissed'.",
"currentState": "dismissed"
}