Streaming & events

POST /v1/research always responds as a Server-Sent Events (SSE) stream. You receive the model's reasoning, its searches, and the answer as they happen, then a final event with the complete result.

The stream format

The response has Content-Type: text/event-stream. Each event is a single data: line containing a JSON object. Every object shares the same envelope:

json
{
  "type": "research.answer.delta",
  "request_id": "req_9f2c1a7b8d3e",
  "timestamp": "2026-01-15T10:30:45.123Z",
  "data": { "content": "…", "sequence": 42 }
}

The stream ends with a literal data: [DONE] line. Stop reading when you see it (or a terminal research.failed event).

Disable client-side buffering so events surface immediately — e.g. curl's -N flag, or reading response.body as a stream in fetch. Bahith already sends X-Accel-Buffering: no.

Event types

research.startedeventoptional

Emitted once at the start. data: query, reasoning_level, max_iterations.

research.thinking.deltaeventoptional

A fragment of the model's reasoning. data: iteration, content, sequence. Suppressed when options.include_thinking is false.

research.tool_call.startedeventoptional

A search/browse tool was invoked. data: tool_id, tool_name, query, parameters.

research.tool_call.completedeventoptional

A tool finished. data: tool_id, runtime_ms, success, sources_found.

research.progresseventoptional

Iteration heartbeat. data: iteration, total_iterations, elapsed_ms, sources_collected, status.

research.answer.deltaeventoptional

A chunk of the final answer, streamed token-by-token. data: content, sequence.

research.completedeventoptional

Terminal success. data: answer, answer_clean, citations, sources_summary, usage. See the API Reference.

research.failedeventoptional

Terminal error. data: error_type, error_code, message, details.

The research.completed and research.failed events are terminal — exactly one of them precedes [DONE].

Consuming the stream

bash
curl -s -N -X POST https://api.bahith.dev/v1/research \
  -H "Content-Type: application/json" \
  -H "X-API-Key: $BAHITH_API_KEY" \
  -d '{ "query": "What is RLHF?" }' | while read -r line; do
    if [[ "$line" =~ ^data:\ (.*) ]]; then
      payload="${BASH_REMATCH[1]}"
      if [ "$payload" = "[DONE]" ]; then
        break
      fi
      type=$(echo "$payload" | jq -r '.type')
      if [ "$type" = "research.answer.delta" ]; then
        echo -n "$(echo "$payload" | jq -r '.data.content')"
      elif [ "$type" = "research.failed" ]; then
        echo "Error: $(echo "$payload" | jq -r '.data.message')"
        exit 1
      fi
    fi
  done

Buffer partial lines. TCP chunks don't align with event boundaries, so accumulate bytes and split on newlines (as above) rather than assuming each read is one complete event.

Handling failures

If research fails mid-stream, you receive a research.failed event before [DONE] instead of research.completed:

json
{
  "type": "research.failed",
  "request_id": "req_9f2c1a7b8d3e",
  "timestamp": "2026-01-15T10:31:00.000Z",
  "data": {
    "error_type": "internal_error",
    "error_code": "INTERNAL_ERROR",
    "message": "…"
  }
}

Request-level rejections (auth, credits, rate limits) are returned as HTTP error responses before the stream begins — see Errors & limits.