Text analysis
Text analysis runs the Perceive8 intelligence pipeline (transcript segments, embeddings, enrichment, alerts, and playbook reports) on raw text instead of audio. Use it for chat logs, email threads, or transcripts produced by other systems. You can submit text three ways: upload a conversation file for asynchronous processing, post inline text for synchronous analysis, or open a WebSocket for real-time streaming with live alerts.
All endpoints require a workspace-scoped credential (X-API-Key: pk_live_xxxxxxxxxxxx or Authorization: Bearer <jwt>; see Authentication). Text processing consumes text credits — 1 credit per 1,000 tokens (rounded up) — and requests fail with 402 when the workspace has insufficient credits.
Upload a file for async analysis
POST /v1/text/upload
Uploads a conversation file and queues it for background analysis. Returns immediately with status: "pending"; poll the status endpoint or fetch the result once completed.
The request body is multipart/form-data:
| Field | Type | Required | Description |
|---|---|---|---|
file |
file | yes | The conversation file to analyze. |
format |
string | yes | File format: jsonl, txt, or csv (case-insensitive). |
playbook_id |
string (UUID) | no | Playbook to evaluate. Falls back to the user's active playbook when omitted. |
language |
string | no | Language code. Default "en". |
Accepted file layouts:
jsonl— one JSON object per line. Text is read fromtext,content, ormessage; speaker fromspeaker,role,from, orname; optionalstart_time/end_time(seconds) or a singletimestamp.txt— one turn per block, e.g.Speaker: text,[00:00] Speaker: text,Speaker - text,Speaker> text,[Speaker] text, orSpeaker:: text. Consecutive lines without a speaker marker continue the previous turn.csv— header row required. Speaker column: one ofspeaker,role,from,name,user. Text column: one oftext,content,message,body. Optional start column (start_time,start,timestamp,time) and end column (end_time,end,duration).
Speaker names are normalized: user/customer/client/human become Person, and assistant/agent/bot/system become Agent. Segments without timestamps are assigned estimated times from word count.
curl -X POST https://api.perceive8.com/v1/text/upload \
-H "X-API-Key: pk_live_xxxxxxxxxxxx" \
-F "[email protected]" \
-F "format=jsonl" \
-F "language=en"
Response — 202 Accepted
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"status": "pending",
"createdAt": "2026-01-15T10:30:00.123456"
}
Errors: 400 (unsupported format, empty file, unparseable content, no valid segments, or malformed playbook_id), 402 (insufficient text credits), 404 (playbook not found or not owned by the user).
Analyze inline text synchronously
POST /v1/text/analyze
Analyzes a single block of text synchronously and returns once the pipeline has finished. The text is stored as a single transcript segment. If a playbook is resolved, a report is generated automatically.
Request body
{
"text": "Thanks for calling. I'd like to cancel my subscription.",
"language": "en",
"playbook_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"speaker": "Person"
}
| Field | Type | Required | Description |
|---|---|---|---|
text |
string | yes | The text to analyze. Must not be blank. |
language |
string | no | Language code. Default "en". |
playbook_id |
string (UUID) | no | Playbook to evaluate. Falls back to the user's active playbook when omitted. |
speaker |
string | no | Speaker label for the segment. Default "Person". |
Response — 200 OK
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"name": null,
"language": "en",
"created_at": "2026-01-15T10:30:00.123456",
"audio_intelligence_features": null,
"key_phrases": null,
"metadata": null
}
The name, audio_intelligence_features, key_phrases, and metadata fields are part of the shared analysis schema and are null for text analyses. Note this endpoint uses snake_case created_at, unlike the camelCase createdAt returned by the other endpoints on this page.
Errors: 400 (missing/blank text or malformed playbook_id), 402 (insufficient text credits), 404 (playbook not found), 502 (the analysis pipeline failed; the analysis is marked failed).
Poll processing status
GET /v1/text/{analysis_id}/status
Returns the processing status of a text analysis created via upload, analyze, or stream.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
analysis_id |
path | string (UUID) | The analysis ID. |
Response — 200 OK
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"status": "completed",
"createdAt": "2026-01-15T10:30:00.123456"
}
status is one of pending, processing, completed, or failed. Returns 404 when the ID is not a valid UUID or no analysis with that ID exists in the workspace.
Fetch the result
GET /v1/text/{analysis_id}/result
Returns the transcript segments and, when a playbook was evaluated, the generated report.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
analysis_id |
path | string (UUID) | The analysis ID. |
Response — 200 OK
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"status": "completed",
"language": "en",
"createdAt": "2026-01-15T10:30:00.123456",
"segments": [
{
"speaker": "Person",
"text": "Thanks for calling. I'd like to cancel my subscription.",
"startTime": 0.0,
"endTime": 6.5,
"confidence": null
}
],
"report": {
"id": "9b2f4c10-3f4a-4c6e-9f0a-1d2e3f4a5b6c",
"status": "completed",
"executiveSummary": "The customer requested a subscription cancellation.",
"overallAssessment": "Churn risk identified; retention playbook steps partially covered."
}
}
Segment fields:
| Field | Type | Description |
|---|---|---|
speaker |
string | Speaker label; "UNKNOWN" when none was set. |
text |
string | Segment text. |
startTime / endTime |
number | Segment bounds in seconds (estimated from word count for text input). |
confidence |
number | null | Transcription confidence; null for text input. |
report is null when no report was generated (for example, when no playbook was active). Returns 404 for unknown analyses.
List text uploads
GET /v1/text/uploads
Lists text-only analyses for the authenticated workspace (analyses backed by an audio file are excluded), most recent first.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
limit |
query | integer | Max items to return. Default 100. |
offset |
query | integer | Number of items to skip. Default 0. |
Response — 200 OK
{
"analyses": [
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"language": "en",
"status": "completed",
"createdAt": "2026-01-15T10:30:00.123456"
}
],
"total": 1
}
Stream text in real time
WS /v1/text/stream
Opens a real-time text streaming session. Each message you send is analyzed immediately (keyword fast-path alerts, LLM sentiment/toxicity/emotion/topics, playbook question evaluation) and the results are pushed back as events. When the stream ends, the full intelligence pipeline runs over the accumulated text and a report is generated if a playbook was set.
Connection
wss://api.perceive8.com/v1/text/stream?token=pk_live_xxxxxxxxxxxx
| Query parameter | Required | Description |
|---|---|---|
token |
yes | API key (pk_live_...) or user JWT. |
x_workspace_id |
no | Workspace UUID. Defaults to the user's personal workspace; you must be a member of the workspace given. |
Authentication failures close the socket with code 4001 and a reason of Missing token, Invalid token, Invalid workspace id, Workspace access denied, or Invalid user identifier. The server sends {"type": "ping", "timestamp": <unix-ms>} every 30 seconds as a heartbeat.
Client messages
| Message | When to send | Fields |
|---|---|---|
start |
First message; initializes the session, creates the analysis, and loads playbook questions. | action: "start", language (optional, default "en"), playbook_id (optional UUID), metadata (optional object, stored on the analysis) |
text |
One message per conversation turn, after start succeeded. |
action: "text", text (string), speaker (optional, default "Person") |
stop |
Ends the stream; triggers post-session processing. | action: "stop" |
Server events
| Event | When sent | Payload fields |
|---|---|---|
session_started |
After start is processed. |
session_id, analysis_id |
transcript_partial |
For each text message. |
data: speaker, text, start_time, end_time (seconds, estimated) |
analysis_chunk |
For each text message, after transcript_partial. |
segment_index, transcript, speaker, sentiment (positive | neutral | negative), sentiment_score (-1.0–1.0), toxicity_score (0.0–1.0), emotion, topics (array of strings) |
alert |
For each triggered alert (playbook or fast-path), after analysis_chunk. |
data — see alert payloads below |
analysis_started |
After stop, when post-session pipeline processing has completed. |
analysis_id, message ("Text analysis completed") |
session_ended |
Last event before the socket closes. | session_id, analysis_id (null if start never succeeded) |
error |
Session setup or post-session failure. | message |
Playbook alerts (from playbook question evaluation) carry these data fields:
| Field | Description |
|---|---|
id |
Alert UUID. |
analysis_id, user_id |
Owning analysis and user. |
playbook_id, question_id |
Triggering playbook and question. |
severity_level |
CRITICAL, HIGH, WARNING, or INFO (derived from confidence). |
message |
The playbook question text (truncated to 200 chars). |
evidence_text |
The segment text that triggered the alert (truncated to 500 chars). |
rationale |
Explanation string. |
recommendation |
Recommended action, or null. |
speaker_name |
Speaker of the triggering segment. |
start_time, end_time |
Segment bounds in seconds. |
confidence |
LLM confidence, 0.0–1.0. |
acknowledged |
Always false on emission. |
created_at |
ISO 8601 timestamp. |
segment_id |
Related segment ID, or null. |
Fast-path alerts (regex keyword matching) have a smaller data payload: id, alert_type (toxic_language, violence, or self_harm), severity (HIGH for toxic_language, CRITICAL otherwise), message, and evidence_text (the matched excerpt).
Example session
→ {"action": "start", "language": "en", "playbook_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7"}
← {"event": "session_started", "session_id": "1c8f…", "analysis_id": "3fa8…"}
→ {"action": "text", "text": "I'm extremely unhappy with this service.", "speaker": "Customer"}
← {"event": "transcript_partial", "data": {"speaker": "Customer", "text": "I'm extremely unhappy with this service.", "start_time": 0.0, "end_time": 3.5}}
← {"event": "analysis_chunk", "segment_index": 0, "transcript": "I'm extremely unhappy with this service.", "speaker": "Customer", "sentiment": "negative", "sentiment_score": -0.8, "toxicity_score": 0.05, "emotion": "anger", "topics": ["complaint", "service quality"]}
→ {"action": "stop"}
← {"event": "analysis_started", "analysis_id": "3fa8…", "message": "Text analysis completed"}
← {"event": "session_ended", "session_id": "1c8f…", "analysis_id": "3fa8…"}
After session_ended, use GET /v1/text/{analysis_id}/result with the returned analysis_id to fetch the final segments and report.