Live, attributed earnings-call transcripts delivered as Server-Sent Events. 99% accuracy from Plio Voice AI. Same wire format powers our own subscriber UI; no internal abstractions.
Every subscriber gets an API key (ek_*). Pass it via the Authorization: Bearer header on REST, or ?token= query on SSE (the browser's native EventSourcecan't set headers).
# Live SSE stream curl -N \ -H "Authorization: Bearer ek_xxxxxxxxxxxxxxxxxxxxxxxx" \ https://pliioai.com/api/live/nvda-q4-fy26/stream # Historical range curl -H "Authorization: Bearer ek_xxxxxxxxxxxxxxxxxxxxxxxx" \ "https://pliioai.com/api/live/nvda-q4-fy26/events?from=0&to=100" # Call metadata curl -H "Authorization: Bearer ek_xxxxxxxxxxxxxxxxxxxxxxxx" \ https://pliioai.com/api/live/nvda-q4-fy26/meta
from plioai_live_earnings import LiveEarningsClient
client = LiveEarningsClient(token="ek_xxxxxxxxxxxxxxxxxxxxxxxx")
# Call metadata
meta = client.meta("nvda-q4-fy26")
print(meta.ticker, meta.status) # "NVDA" "ended"
# Live SSE stream — iterate over envelopes as they arrive
for env in client.stream("nvda-q4-fy26"):
ev = env.event
if ev.type == "attribution":
print(f"[{ev.name}] {ev.text}")
elif ev.type == "phase_transition":
print(f"--- phase: {ev.to_phase} ---")
elif ev.type == "done":
break
# Historical backfill
for env in client.events("nvda-q4-fy26", from_=0, to=999_999):
...Install: pip install plioai-live-earnings
import { LiveEarningsClient } from "@plioai/live-earnings";
const client = new LiveEarningsClient({
token: "ek_xxxxxxxxxxxxxxxxxxxxxxxx",
});
// Call metadata
const meta = await client.meta("nvda-q4-fy26");
console.log(meta.ticker, meta.status); // "NVDA" "ended"
// Live SSE stream — async iterator
for await (const env of client.stream("nvda-q4-fy26")) {
const ev = env.event;
if (ev.type === "attribution") {
console.log(`[${ev.name}] ${ev.text}`);
} else if (ev.type === "done") {
break;
}
}
// Historical backfill
const { events } = await client.events("nvda-q4-fy26", { from: 0, to: 1000 });Install: npm i @plioai/live-earnings
/meta, /events, /sections, /report, /predictions)/stream, /partial-stream, /audio-stream — cycle 2 tiers only; contact Plio for access)Every envelope on /stream looks like:
{
"tenantId": "plio-live",
"callSlug": "nvda-q4-fy26",
"seq": 184,
"publishedAt": "2026-05-19T22:14:03.142Z",
"event": { ... }
}seq is monotonic per (tenant, call). Use it to detect gaps after a reconnect — fetch the missed range via /events?from=<last_seq> then re-attach.
attribution — a finalized speaker turn{
"type": "attribution",
"turnId": "nvda-q4-fy26-184",
"name": "Jensen Huang",
"source": "plio-live",
"text": "We want to take the great opportunity...",
"phase": "qa"
}name is the canonical speaker name. source is always "plio-live" — no internal pipeline labels leak.
unknown— a turn we couldn't attribute confidentlySame shape as attribution but without name. Typically followed by a rethink (same turnId, now with a name) within seconds.
phase_transition — section boundary{
"type": "phase_transition",
"fromPhase": "ir_intro",
"toPhase": "cfo_remarks",
"triggerTurnId": "nvda-q4-fy26-37"
}Phases: operator_opening, ir_intro, cfo_remarks, ceo_remarks, qa, closing.
ai_insight — model-surfaced moment{
"type": "ai_insight",
"turnId": "nvda-q4-fy26-211",
"insight": "Q1 FY27 revenue guide of $43B is +5.5% QoQ — above sell-side consensus of $41.6B. Watch the gross-margin commentary on the next CFO turn.",
"category": "guidance",
"frame": "Data-center momentum vs. sequential growth slowdown narrative.",
"watchFor": "Whether Colette frames this as Hopper transition or Blackwell pull-in."
}Fires alongside high-signal turns — guidance moves, analyst pressure, CFO numbers, deviation from prior guidance. turnId references the attribution event the insight is anchored to. category is a coarse bucket (e.g. guidance, surprise, pressure). frame and watchFor are optional additional context; consume only the fields you need.
turn_speaker_amend — late correction// Seq 184 — original attribution
{ "seq": 184, "event": { "type": "attribution",
"turnId": "t-200", "name": "Speaker A", "text": "..." } }
// Seq 213 — corrected later (dedicated event type)
{ "seq": 213, "event": { "type": "turn_speaker_amend",
"turnId": "t-200", "newName": "Stacy Rasgon" } }Post-publish corrections to a turn's speaker label arrive as their own event type — not as a second attribution. The amend references the original turnId and supplies newName. Apply in-place: the corrected turn keeps its original chronological position. Latest amend per turnId wins.
Legacy note: calls that ran before 2026-05-21 may carry a second attribution event on the same turnId instead (the old amend path). Treat either pattern as a correction — the latest signal per turnId is canonical.
done — call endedClose your stream connection on receipt.
SSE connections drop. Reverse proxies idle them, browsers background-tab them, mobile networks fail. The wire contract is built so a reconnect never loses data — every envelope has a monotonic seq per (tenant, call), and any range is backfillable via GET /events.
seq you successfully consumed from /stream (the SSE id: line on each envelope, equal to envelope.seq).GET /events?from=<last+1>&to=999999999 in a loop, following the nextFrom cursor untilnextFrom is null. Each response is capped at limit envelopes (default 1000, max 5000).// 1) Track the highest seq we've consumed from /stream
let lastSeq = 0;
const stream = new EventSource(
`https://pliioai.com/api/live/${SLUG}/stream?token=${TOKEN}`
);
stream.addEventListener("envelope", (e) => {
const env = JSON.parse(e.data);
handle(env);
lastSeq = env.seq;
});
// 2) On error/close, paginate /events from lastSeq+1
stream.onerror = async () => {
stream.close();
let from = lastSeq + 1;
while (true) {
const r = await fetch(
`https://pliioai.com/api/live/${SLUG}/events?from=${from}&to=999999999`,
{ headers: { Authorization: `Bearer ${TOKEN}` } }
);
if (!r.ok) throw new Error(`backfill ${r.status}`);
const { events, nextFrom } = await r.json();
for (const env of events) {
handle(env);
lastSeq = env.seq;
}
if (nextFrom === null) break; // caught up
from = nextFrom; // continue pagination
}
// 3) Reopen the live stream from the live edge
reconnect();
};The 1000-envelope default is a real limit, not advisory. Long calls (90+ min, Q&A-heavy) commonly cross seq=2000; a single un-paginated fetch will silently truncate. Loop on nextFromor you'll lose turns.
A separate, non-streaming endpoint returns the latest set of predicted analyst questions for a call. Useful for IR-side dashboards that surface predicted Q&A before and during the event.
GET https://pliioai.com/api/live/{slug}/predictions
Authorization: Bearer ek_xxxxxxxxxxxxxxxxxxxxxxxxReturns the latest prediction_runs row for drop_number=1 plus its prediction_items ordered by rank ascending. Prefers status="complete" runs; falls back to the latest pending / error run so consumers can render in-flight states.
Predictions are generated at three points relative to the call. Only Drop 1 is live in production today; Drops 2 and 3 layer onto the same response shape and ship as fast-follow.
{
"run": {
"id": "e1786274-517e-47c2-8e51-78358981877d",
"tenantId": "plio-live",
"callSlug": "mrvl-q1-fy27",
"dropNumber": 1,
"runSeq": 2,
"status": "complete",
"createdAt": "2026-05-27T20:18:42.123Z",
"errorMessage": null
},
"items": [
{
"id": "12ab34cd-...",
"rank": 1,
"analystName": "Vivek Arya",
"analystFirm": "Bank of America",
"predictedQuestion": "Matt, you guided custom to double in FY27...",
"whyReasoning": "Arya pairs a near-term clarifier with a strategic exclusivity probe...",
"peerEvidence": [
{ "company": "Marvell", "quarter": "q4-fy26", "question": "..." },
{ "company": "Marvell", "quarter": "q3-fy26", "question": "..." }
],
"triggerLanguage": "custom business reached $1.5 billion in fiscal 2026...",
"bestAnswerFrame": "Lead with PO coverage on next-gen XPU...",
"likelyFollowUp": "So if the second customer slips a quarter or two...",
"confidence": 0.82
}
]
}rank — integer 1-N, unique. 1 = highest joint likelihood × strategic importance.analystName / analystFirm — the analyst most likely to ask.predictedQuestion— literal question phrased in the analyst's voice.whyReasoning — why this analyst, why this question, why now.peerEvidence— cross-company precedents where the same analyst (or a peer) asked an analogous question on another company's call.triggerLanguage — verbatim release sentence that motivates the question.bestAnswerFrame — how mgmt should frame the answer.likelyFollowUp — what the analyst likely asks if mgmt is evasive.confidence — 0.00-1.00 joint probability estimate.complete runs ship with Cache-Control: private, max-age=10; pending and error runs ship no-cache. Consumer pattern: poll every ~10 s while the run is pending or the endpoint returns 404; oncecomplete, cache the result.
async function poll(slug, token) {
const r = await fetch(`https://pliioai.com/api/live/${slug}/predictions`, {
headers: { Authorization: `Bearer ${token}` },
});
if (r.status === 404) return { run: null, items: [] };
if (!r.ok) throw new Error(`predictions ${r.status}`);
return r.json();
}
const data = await poll("mrvl-q1-fy27", TOKEN);
if (data.run?.status === "complete") {
// render data.items, sorted by rank ASC
}Full field-by-field contract in the OpenAPI spec (PredictionsResponse, PredictionRun, PredictionItem, PeerEvidence schemas).
A token grants access to one call. Cross-call use returns 401. The query-string form (?token=) is provided for browser EventSource; prefer the header form everywhere else.
401 unauthorized — token missing, revoked, or scoped to a different call.403 forbidden — cookie-authed user without a subscription row for this call (predictions endpoint only).400 invalid_range — from > to on /events.404 not_found — unknown call slug.404 no_predictions — predictions endpoint only; no prediction_runs row exists for the call yet.5xx — internal error; retry with exponential backoff.The wire format is stable per the published OpenAPI / AsyncAPI specs. We treat shipped subscribers' consumers as a contract, not a hypothesis.
?version= query parameter for the transition window. Example: if we rename turnId → turn_id, the new shape lands at ?version=v1.1 alongside the existing v1.0 for ≥ 90 days before v1.0 is retired.Production integrations should pin the OpenAPI / AsyncAPI spec version they were built against and re-validate when they upgrade. We don't version the URL; we version the wire shape.
2026-05-26 — Auth required: GET /api/live/<slug>/report now requires a valid subscriber token (Bearer header or ?token= query) — mirrors /meta + /events. Returns 401 without a token. Endpoint behavior, response shape, and tenant scoping are unchanged. Documented in the OpenAPI spec linked from Reference.2026-05-26 — New endpoint: GET /api/live/<slug>/predictions. Returns the latest set of predicted analyst questions for a call. See "Predictions" above. V1 ships Drop 1 (pre-call) only; Drops 2 (end of prepared remarks) and 3 (per-Q&A) layer onto the same response shape as fast-follow.2026-05-21 — New event type: turn_speaker_amend. Post-publish speaker corrections now ship as a dedicated event rather than a duplicate attribution. See "Late corrections" above. Legacy duplicate-attribution entries remain in historical calls.2026-05-21— End-of-turn silence threshold tuned from 400 ms → 300 ms. Tighter Q&A handoffs; slightly more turns per call. Wire shape unchanged.2026-05-21— Per-session insight cap raised from 50 → 200. Long Q&A sections no longer go silent on ai_insight mid-call.Cycle-2 hardening. Items below are committed to ship before the next paid pilot (target: early June 2026). Wire shape changes will be additive — no breaking field removals.
/stream. Subscribers can tell at a glance whether their stream is live, replaying gaps, or stalled. Today this requires watching seqdrift; soon it's an explicit signal./partial-stream reconnect with Last-Event-ID. The live (provisional) partial-transcript stream gets the same gap-replay semantics /stream already has — no more dropped partials when a reverse proxy idles your connection.done: attribution latency p50/p95/worst, named-attribution rate, unknown rate, correction rate per call. Reachable via GET /api/live/<slug>/report.confidencefield (0–1). Existing consumers ignore it; agents that care can gate on a threshold.