Read a verification stream
Keep complete events across network chunks and detect interrupted responses.
Read event types
The response uses text/event-stream, with a JSON object in each data: frame. Events can include citation, groups, verdict, decomposition, sub-claim-start, sub-claim-result, metadata, and result. Treat result as the final structured verification payload.
A separate usage event carries operation_id and usage.credits. done marks successful completion. error reports a verification failure; accounting_error reports a billing failure. Do not turn an interrupted stream into a successful result.
Keep partial frames
A network chunk can end in the middle of a UTF-8 character, a JSON value, or a frame separator. Retain incomplete data until the next read. This example accepts the API's LF and CRLF framing, fails on error events, and limits buffered data to 8 MiB. Increase that client limit only when your expected results require it.
// This function is also published verbatim in the streaming guide.
async function* readEvents(response) {
if (!response.ok)
throw new Error(`HTTP ${response.status}: ${await response.text()}`);
if (!response.body) throw new Error("Response body is unavailable");
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = "";
let completed = false;
try {
while (true) {
const { value, done } = await reader.read();
buffer += decoder.decode(value, { stream: !done });
if (buffer.length > 8 * 1024 * 1024)
throw new Error("SSE frame exceeds the client limit");
let boundary;
while ((boundary = /\r?\n\r?\n/.exec(buffer))) {
const frame = buffer.slice(0, boundary.index);
buffer = buffer.slice(boundary.index + boundary[0].length);
const data = frame
.split(/\r?\n/)
.filter((line) => line.startsWith("data:"))
.map((line) => line.slice(5).replace(/^ /, ""))
.join("\n");
if (!data) continue;
const event = JSON.parse(data);
if (event.type === "error" || event.type === "accounting_error") {
throw new Error(event.message || event.type);
}
yield event;
if (event.type === "done") {
completed = true;
return;
}
}
if (done) break;
}
if (!completed) throw new Error("Stream ended without a done event");
} finally {
await reader.cancel();
reader.releaseLock();
}
}Consume the stream
Call this from your server with a key in its environment. Set a timeout appropriate to your request.
const response = await fetch("https://api.webcite.co/api/v1/verify/stream", {
method: "POST",
headers: {
"x-api-key": process.env.WEBCITE_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify({ claim: "The Eiffel Tower is 330 meters tall" }),
signal: AbortSignal.timeout(300_000),
});
for await (const event of readEvents(response)) {
console.log(event);
}