Skip to main content
Failures in a Research Agent or Workflows request fall into two layers, and they require different handling. HTTP-level errors arrive before the SSE stream starts and surface as conventional HTTP status codes. Stream-level errors arrive inside the SSE stream as typed messages and may or may not terminate the request. This guide covers both, and the recommended response for each.

HTTP-level errors

These are returned by the API before any SSE event is emitted. The response body is typically a JSON object with a detail field (for validation errors) or a plain error message. Your HTTP client should check the response status before consuming the stream. Use a small wrapper such as response.raise_for_status() (in Python’s requests) to convert HTTP errors into exceptions before entering the streaming loop. Trying to consume the SSE stream from a non-2xx response will silently produce zero events.

Stream-level errors

Once the HTTP response is established with a 2xx status, errors are delivered as typed messages inside the stream. Three message types matter:

LLM_RETRY

The agent’s upstream LLM call hit a transient failure (rate limit, timeout, short-lived error) and is being retried automatically. The stream will resume without further action.
Consumers should not act on this event other than emitting a debug-level log line. There is no user-facing surface beyond an optional “agent is retrying…” indicator.

TOOL_ERROR

A specific tool failed. The tool_name field identifies which one. The agent will continue, possibly calling a different tool or synthesizing without that source. Most integrations log these events and only surface a user-visible warning if the final answer is materially degraded (for example, if every search call failed).
A common pattern is to count TOOL_ERROR events per tool name and, if the count exceeds a threshold during the same request, surface a soft warning at the end (“Some sources could not be retrieved; the answer may be incomplete.”).

ERROR

The request cannot continue. The stream terminates immediately after this event; no COMPLETE will follow. Surface the error to the user and stop reading the stream.
The error field is a human-readable string suitable for logging, but not always suitable for direct display to end users. Wrap it in your own UX-friendly message where appropriate.

Worked example: error-aware streaming handler

The handler below distinguishes HTTP-level failures, stream-level errors, and informational retries. It logs per-event details and only raises when the request can no longer succeed.
This handler intentionally distinguishes:
  • Pre-stream HTTP errors — raised immediately so callers do not waste cycles reading an empty stream.
  • In-stream recoverable events — logged at appropriate levels but never raised.
  • In-stream fatal events — raised so callers can surface the failure.
  • Truncated streams — detected by the absence of a COMPLETE event, preventing silent “empty answer” bugs.

Asynchronous workflow runs

A submitted workflow (POST /v1/workflow/execute/async) moves the failure surface off the HTTP request. The submit returns 202 as soon as the run is accepted, so a 2xx there says nothing about whether the run succeeds. The outcome arrives as the run’s status when you retrieve it: completed, or error with the failing ERROR event in its events list. Three consequences for error handling:
  • A dropped connection is not a failure. Closing or losing the event stream never stops the run. Reconnect with the Last-Event-ID header, or stop streaming and retrieve the result instead. Nothing is lost either way.
  • 409 means the run is in the wrong state, not that your request was malformed. On the stream and cancel endpoints it means the run has already finished, so read its result from GET /v1/workflow/executions/{execution_id}. On submit it means the execution_id you asked to continue cannot be continued, because it is still in progress or has already completed. Do not retry as-is in either case.
  • 503 is worth retrying, unlike most 4xx and 5xx pairs. It means the run was accepted but is not ready to attach to or cancel yet. Retry the same request after the number of seconds given in Retry-After.
See Running a workflow for the full flow.

Retry and backoff

For 429 and 5xx HTTP responses, exponential backoff with jitter is the safe default:
Do not retry on 400, 401, 403, 404, or 409: those reflect client-side, identity, or resource-state problems that will not resolve on retry. In-stream events do not benefit from retry at the request level. LLM_RETRY and TOOL_ERROR already represent the agent’s own internal retry behavior; the request is doing the right thing without your help.

When to abort versus continue

Next steps

Streaming responses

Full reference for every message type the stream may emit.

Conversation continuity

Recover from a 404 on from_checkpoint_id by resetting the thread.