Provn

Streaming

Server-sent events, with the signed receipt as the last event before [DONE].

Run / streaming

Add "stream": true and the gateway answers with text/event-stream. Chunks follow the OpenAI format. After the last chunk, Provn sends a provn.receipt event, then data: [DONE].

bash
curl -N https://YOUR-PROVN-HOST/v1/chat/completions \
  -H "Authorization: Bearer $PROVN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "provn",
    "stream": true,
    "messages": [{ "role": "user", "content": "Name one prime number." }]
  }'
End of the stream
data: {"id":"chatcmpl-...","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":" and 7."},"finish_reason":null}]}

data: {"id":"chatcmpl-...","object":"chat.completion.chunk","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}

event: provn.receipt
data: {"receipt":"eyJ...","signature":"0x...","signer":"0x..."}

data: [DONE]

The receipt event carries the same payload format as the x-provn-receipt header on a buffered call. The gateway sends headers before the stream starts, so a streamed call carries its receipt in the body.

Read the receipt event

Some SDK stream helpers skip named events. To keep the receipt, read the raw stream and watch for event: provn.receipt.

ts
type ReceiptEvent = { receipt: string; signature: string; signer: string };

const res = await fetch("https://YOUR-PROVN-HOST/v1/chat/completions", {
  method: "POST",
  headers: {
    Authorization: "Bearer " + process.env.PROVN_API_KEY,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    model: "provn",
    stream: true,
    messages: [{ role: "user", content: "Name one prime number." }],
  }),
});

const reader = res.body!.pipeThrough(new TextDecoderStream()).getReader();
let buffer = "";
let receipt: ReceiptEvent | null = null;

for (;;) {
  const { value, done } = await reader.read();
  if (done) break;
  buffer += value;
  const events = buffer.split("\n\n");
  buffer = events.pop() ?? "";
  for (const block of events) {
    const lines = block.split("\n");
    const data = lines.find((l) => l.startsWith("data: "))?.slice(6);
    if (lines.includes("event: provn.receipt") && data) receipt = JSON.parse(data);
  }
}

console.log(receipt);

provn-nano

provn-nano emulates streaming. Provn waits for the full upstream response and then sends it as chunks, so the first chunk arrives once the whole answer exists. provn streams from the upstream as tokens come in.