Search Agent API

This is for:

Developer
Important

Coveo Conversational Search is a paid product extension that enables the Coveo Search Agent. Contact Coveo Sales or your Account Manager to add Coveo Conversational Search to your organization license.

You can use the Coveo Search Agent to add a conversational search experience to a Coveo-powered search interface. It uses agentic AI capabilities, such as reasoning and decision-making, to orchestrate multiple rounds of content retrieval and answer generation based on follow-up questions.

If you’re building your interface with one of the Coveo Search UI libraries (Atomic, Headless, Quantic), you don’t need to call the Search Agent API directly. These libraries handle the Agent API integration internally (see Configure a search interface for the Search Agent). This article is for developers who need to call the API at the HTTP level without relying on a Coveo Search UI library (for example, for native mobile applications or other non-JavaScript environments).

Migration from /preview to /v1

The Search Agent API has moved from the Preview route to a stable, versioned GA route. All production integrations must use /v1 API by September 30, 2026.

For example:

The request and response schemas are unchanged. Only the route prefix changes.

If you use Coveo UI components (Atomic, Headless, Quantic, or Coveo for Salesforce), upgrade to a version that targets /v1 (no code changes required).

Prerequisites

Before you begin, make sure you have:

  • An access token: Either an API key for the Search API that was created using the Anonymous search template or a search token that grants the Allowed access level on the Execute queries domain.

    Pass the token as a bearer token in the Authorization header of each request:

    Authorization: Bearer <MY_TOKEN>
  • A Search Agent configured in your organization. You’ll need the agent’s ID for all API requests documented in this article.

    Tip

    To get the Search Agent ID, access the Agents (platform-ca | platform-eu | platform-au) page of the Coveo Administration Console, click the Search Agent, and then click View.

  • Incremental event parsing. The Search Agent API is streaming only. Responses are delivered as Server-Sent Events (SSE) and there’s no option to receive the full response in a single payload. Your client must support incremental event parsing.

Generate a head answer

To generate an initial answer, send a POST request to the following endpoint.

POST https://<ORG_ID>.org.coveo.com/api/v1/organizations/<ORG_ID>/agents/<AGENT_ID>/answer HTTP/1.1

Content-Type: application/json
Accept: text/event-stream, application/json
Authorization: Bearer <MY_TOKEN>

Where:

  • <ORG_ID> is your Coveo organization ID.

  • <AGENT_ID> is the unique identifier of the agent you want to query.

Request body

The request body is a JSON object. The only required field is q: the user’s question (maximum 300 characters). For the full list of accepted fields, see the Knowledge Generative AI API reference.

Example request

curl -X POST \
  'https://<ORG_ID>.org.coveo.com/api/v1/organizations/<ORG_ID>/agents/<AGENT_ID>/answer' \ 1
  -H 'Authorization: Bearer <MY_TOKEN>' \ 2
  -H 'Content-Type: application/json' \
  -H 'Accept: text/event-stream, application/json' \ 3
  -d '{
    "q": "How do I reset my password?" 4
  }'
1 Replace <ORG_ID> with your organization ID and <AGENT_ID> with your Search Agent’s ID.
2 Replace <MY_TOKEN> with your access token (API key or search token). See Prerequisites for details.
3 The Accept header must include text/event-stream to receive the SSE stream. Including application/json (as shown) is also important so that validation errors are returned as JSON rather than a 406 Not Acceptable response.
4 The user question (maximum 300 characters): the only required field.

Response format

The response is a Server-Sent Events (SSE) stream. The server sends events incrementally as the answer is generated. The client must parse these events as they arrive rather than wait for the full response.

Each event follows the AG-UI protocol format:

event: TEXT_MESSAGE_CHUNK
data: {"type":"TEXT_MESSAGE_CHUNK","delta":"To reset your password, "}

event: TEXT_MESSAGE_CHUNK
data: {"type":"TEXT_MESSAGE_CHUNK","delta":"navigate to the login page..."}

Parse the event stream

The Agent API streams the response as a sequence of events following the AG-UI protocol. Each event is delivered as a Server-Sent Event with a data: line containing a JSON object. The type field identifies the event type.

For the full list of AG-UI event types, their properties, and flow diagrams, see the AG-UI Events documentation.

The Coveo Search Agent uses the following event types during answer generation:

Event type Description

RUN_STARTED

The agent run has begun. Capture threadId as the conversation ID for follow-ups.

STEP_STARTED / STEP_FINISHED

Generation steps (for example, searching, thinking, answering).

TOOL_CALL_START / TOOL_CALL_ARGS / TOOL_CALL_END

Internal tool invocations (for example, content retrieval). For search tool calls, the TOOL_CALL_ARGS delta field contains a JSON document (serialized as a string) with the search query.

TEXT_MESSAGE_CHUNK

A text fragment of the answer. Accumulate all delta values to build the full answer.

CUSTOM

Metadata events identified by name (for example, header or citations). See Custom events.

RUN_FINISHED

The run completed. Check result.completionReason to determine whether an answer was generated.

RUN_ERROR

An error occurred. See Error handling.

Event payload examples

The following examples show the JSON payload for the most important events.

RUN_STARTED

{"type": "RUN_STARTED", "runId": "conv_q2lp4shyb2w3nyanngvm6ukloi_0001", "threadId": "conv_q2lp4shyb2w3nyanngvm6ukloi"}

TEXT_MESSAGE_CHUNK

{"type": "TEXT_MESSAGE_CHUNK", "delta": "To reset your password, "}

CUSTOM (header)

{"type": "CUSTOM", "name": "header", "value": {"contentFormat": "text/markdown", "followUpEnabled": true, "conversationToken": "eyJ..."}}

CUSTOM (citations)

{"type": "CUSTOM", "name": "citations", "value": {"citations": [{"title": "Reset guide", "uri": "https://...", "text": "..."}]}}

RUN_FINISHED

{"type": "RUN_FINISHED", "result": {"completionReason": "ANSWERED"}}

RUN_ERROR

{"type": "RUN_ERROR", "code": "KNOWLEDGE:SSE_INTERNAL_ERROR", "message": "An internal error occurred"}

Completion reason

The RUN_FINISHED event carries a result.completionReason field that indicates the outcome of the generation:

  • ANSWERED: The agent successfully generated an answer.

  • Any other value: The agent couldn’t produce an answer. This isn’t an error; the stream completed normally, but the agent determined it couldn’t answer the question given the available content.

Your client should check this field to decide whether to display the accumulated answer text or show a "no answer available" message.

Note

The AG-UI protocol defines the event envelope format and streaming semantics. For full protocol details beyond the Coveo-specific events documented here, see the AG-UI protocol.

header custom event

The header custom event is sent early in the stream, before the answer text begins. It provides metadata that your client needs to correctly render the response and enable follow-up conversations.

The value object contains the following fields:

Field Type Description

contentFormat

string

The format of the answer text: text/markdown or text/plain. Use this to determine how to render the accumulated TEXT_MESSAGE_CHUNK deltas.

followUpEnabled

boolean

Whether the agent supports follow-up conversations. If false, don’t offer a follow-up input to the user.

conversationToken

string

A cryptographic proof token that the client must include in follow-up requests to authenticate the continuation of the conversation.

{
  "type": "CUSTOM",
  "name": "header",
  "value": {
    "contentFormat": "text/markdown",
    "followUpEnabled": true,
    "conversationToken": "eyJhbGciOiJIUzI1NiIs..."
  }
}

citations custom event

The citations custom event is sent toward the end of the stream, after all TEXT_MESSAGE_CHUNK events. It contains the source documents the agent used to generate the answer.

The value object contains a citations array. Each citation object has the following fields:

Field Type Description

id

string

A unique identifier for the citation.

title

string

The title of the source document.

uri

string

The original URI of the source document in the index.

clickUri

string

The URI to open when the user clicks the citation link.

permanentid

string

A permanent identifier for the source document, stable across re-indexing.

text

string

A relevant text snippet from the source document that supports the answer.

source

string

The name of the source from which the document was indexed.

{
  "type": "CUSTOM",
  "name": "citations",
  "value": {
    "citations": [
      {
        "id": "cite-1",
        "title": "How to reset your password",
        "uri": "https://example.com/kb/reset-password",
        "clickUri": "https://example.com/kb/reset-password",
        "permanentid": "abc123def456",
        "text": "Navigate to the login page and click Forgot password...",
        "source": "Knowledge Base"
      }
    ]
  }
}

Event sequence

The following ordered list shows the typical progression of events from request to completion.

  1. RUN_STARTED: The run begins. Capture threadId as the conversationId for follow-up requests.

  2. CUSTOM (header): Metadata including conversationToken, contentFormat, and followUpEnabled. Capture conversationToken for follow-ups.

  3. STEP_STARTED (searching): The retrieval step begins.

  4. TOOL_CALL_START: The agent invokes an internal search tool.

  5. TOOL_CALL_ARGS: The tool call arguments (for example, the query the agent formulated).

  6. TOOL_CALL_END: The tool call completes.

  7. STEP_FINISHED (searching): The retrieval step ends.

  8. STEP_STARTED (answering): The answer generation step begins.

  9. TEXT_MESSAGE_CHUNK: A text fragment of the answer. Repeats to accumulate all delta values.

  10. TEXT_MESSAGE_CHUNK: Continues until the full answer is streamed.

  11. CUSTOM (citations): The citations array referencing source documents.

  12. STEP_FINISHED (answering): The generation step ends.

  13. RUN_FINISHED: The run completes. Check completionReason to confirm the answer was generated (ANSWERED).

Notes
  • Not every run includes all events. For example, if the agent can’t find relevant passages, it can skip directly from the searching step to RUN_FINISHED with a completionReason other than ANSWERED.

  • A generated answer can contain inline links that come directly from passages in the indexed content. If your source content uses relative links, these can fail to resolve when rendered outside of the original site. For best practices on avoiding broken links in answers, see Inline links in generated answers.

Stream-parsing algorithm

The following example shows a language-agnostic algorithm that parses the event stream, accumulates the answer text from TEXT_MESSAGE_CHUNK deltas, and extracts the values needed for follow-up requests.

fullText = ""
conversationId = null        1
conversationToken = null     2
contentFormat = null
citations = []

FOR EACH event IN stream:
  SWITCH event.type:

    CASE "RUN_STARTED":
      conversationId = event.threadId   3

    CASE "CUSTOM":
      IF event.name == "header":
        conversationToken = event.value.conversationToken  4
        contentFormat = event.value.contentFormat
      ELSE IF event.name == "citations":
        citations = event.value.citations

    CASE "TEXT_MESSAGE_CHUNK":
      fullText += event.delta           5

    CASE "RUN_FINISHED":
      IF event.result.completionReason == "ANSWERED":
        display(fullText, citations)    6
      ELSE:
        showNoAnswer()

    CASE "RUN_ERROR":
      handleError(event.code, event.message)
1 Hold the conversationID for follow-up requests.
2 Hold the conversationToken for follow-up requests.
3 Extract conversationId from the RUN_STARTED event’s threadId field.
4 Extract conversationToken from the header custom event’s value.conversationToken field.
5 Accumulate each text delta: the full answer is the concatenation of all TEXT_MESSAGE_CHUNK deltas.
6 Once RUN_FINISHED confirms the answer was generated, display the full text along with citations.

After the stream ends, use conversationId and conversationToken in the follow-up request body to continue the conversation.

Full event stream example

The following collapsible block shows a complete, annotated Server-Sent Events (SSE) stream from start to finish. Each event is shown in its raw SSE wire format (event: and data: lines).

Complete event stream example
event: RUN_STARTED
data: {"type":"RUN_STARTED","runId":"conv_q2lp4shyb2w3nyanngvm6ukloi_0001","threadId":"conv_q2lp4shyb2w3nyanngvm6ukloi"}
                                                                        1
event: CUSTOM
data: {"type":"CUSTOM","name":"header","value":{"contentFormat":"text/markdown","followUpEnabled":true,"conversationToken":"eyJhbGciOiJIUzI1NiIs..."}}
                                                                        2
event: STEP_STARTED
data: {"type":"STEP_STARTED","stepName":"searching","startedAt":"2025-07-15T10:00:01Z"}
                                                                        3
event: TOOL_CALL_START
data: {"type":"TOOL_CALL_START","toolCallId":"tc-001","toolCallName":"searchRetrieval","startedAt":"2025-07-15T10:00:01Z"}

event: TOOL_CALL_ARGS
data: {"type":"TOOL_CALL_ARGS","toolCallId":"tc-001","delta":"{\"q\":\"how to reset password\"}"}

event: TOOL_CALL_END
data: {"type":"TOOL_CALL_END","toolCallId":"tc-001","finishedAt":"2025-07-15T10:00:02Z"}

event: STEP_FINISHED
data: {"type":"STEP_FINISHED","stepName":"searching","finishedAt":"2025-07-15T10:00:02Z"}

event: STEP_STARTED
data: {"type":"STEP_STARTED","stepName":"answering","startedAt":"2025-07-15T10:00:03Z"}
                                                                        4
event: TEXT_MESSAGE_CHUNK
data: {"type":"TEXT_MESSAGE_CHUNK","delta":"To reset your "}
                                                                        5
event: TEXT_MESSAGE_CHUNK
data: {"type":"TEXT_MESSAGE_CHUNK","delta":"password, navigate to the "}

event: TEXT_MESSAGE_CHUNK
data: {"type":"TEXT_MESSAGE_CHUNK","delta":"login page and click "}

event: TEXT_MESSAGE_CHUNK
data: {"type":"TEXT_MESSAGE_CHUNK","delta":"**Forgot password**. "}

event: TEXT_MESSAGE_CHUNK
data: {"type":"TEXT_MESSAGE_CHUNK","delta":"Enter your email address "}

event: TEXT_MESSAGE_CHUNK
data: {"type":"TEXT_MESSAGE_CHUNK","delta":"and follow the instructions sent to your inbox."}

event: CUSTOM
data: {"type":"CUSTOM","name":"citations","value":{"citations":[{"id":"cite-1","title":"How to reset your password","uri":"https://example.com/kb/reset-password","clickUri":"https://example.com/kb/reset-password","permanentid":"abc123def456","text":"Navigate to the login page and click Forgot password...","source":"Knowledge Base"}]}}
                                                                        6
event: STEP_FINISHED
data: {"type":"STEP_FINISHED","stepName":"answering","finishedAt":"2025-07-15T10:00:05Z"}

event: RUN_FINISHED
data: {"type":"RUN_FINISHED","result":{"completionReason":"ANSWERED"}}
                                                                        7
1 RUN_STARTED: The run begins. Capture threadId (conv_q2lp4shyb2w3nyanngvm6ukloi) as conversationId.
2 CUSTOM (header): Capture conversationToken for follow-ups. Note contentFormat is text/markdown.
3 Searching step: The agent retrieves relevant passages. Includes a tool call showing the formulated search query.
4 Answering step: The agent begins generating the answer text.
5 TEXT_MESSAGE_CHUNK: Text deltas arrive incrementally. Concatenate all delta values to build the full answer: "To reset your password, navigate to the login page and click Forgot password. Enter your email address and follow the instructions sent to your inbox."
6 CUSTOM (citations): The source documents the agent used to generate the answer.
7 RUN_FINISHED: completionReason is ANSWERED, confirming the answer was successfully generated.

Follow-up conversations

The Agent API supports multi-turn conversations through a follow-up mechanism. After generating a head answer, the client can send follow-up questions that share the same conversational context, allowing the agent to produce answers that build on prior turns.

Important
  • A conversation is limited to 15 total turns, including the initial query. Up to 14 follow-up queries can be submitted after the initial query. Once the 15-turn limit is reached, a new conversation must be started by entering a new query in the main search box.

  • A conversation remains active for up to 48 hours, after which no follow-up queries can be added.

  • Each query is limited to 300 characters, whether it’s the initial query or a follow-up query. Queries that exceed this limit are rejected.

  • By default, an organization is limited to 5,000 Search Agent follow-up queries per day. This limit is adjustable. If your organization needs a higher limit, contact your Coveo Account Manager.

Capture conversation identity from the head answer

To send a follow-up, your client must capture two values from the head answer stream:

Value Source Purpose

conversationId

threadId field in the RUN_STARTED event

Identifies the conversation thread.

conversationToken

value.conversationToken in the header custom event

A cryptographic proof token that authenticates the client as the originator of the conversation.

Both values are required in every follow-up request. See the stream-parsing algorithm for an example showing how to extract these values during stream parsing.

Follow-up endpoint

Send a follow-up question using a POST request to the following endpoint:

POST https://<ORG_ID>.org.coveo.com/api/v1/organizations/<ORG_ID>/agents/<AGENT_ID>/follow-up HTTP/1.1

Content-Type: application/json
Accept: text/event-stream, application/json
Authorization: Bearer <MY_TOKEN>

Where:

  • <ORG_ID> is your Coveo organization ID.

  • <AGENT_ID> is the unique identifier of the agent you want to query.

Request body

The request body requires the follow-up question and the conversation identifiers captured from the head answer stream. For the full schema, see the Knowledge Generative AI API reference.

Example request

The following curl command sends a follow-up question referencing a prior conversation:

curl -X POST \
  'https://<ORG_ID>.org.coveo.com/api/v1/organizations/<ORG_ID>/agents/<AGENT_ID>/follow-up' \ 1
  -H 'Authorization: Bearer <MY_TOKEN>' \
  -H 'Content-Type: application/json' \
  -H 'Accept: text/event-stream, application/json' \
  -d '{
    "q": "What about pricing?", 2
    "conversationId": "conv_q2lp4shyb2w3nyanngvm6ukloi", 3
    "conversationToken": "eyJhbGciOiJIUzI1NiIs..." 4
  }'
1 Replace <ORG_ID> with your organization ID and <AGENT_ID> with your agent’s ID.
2 The follow-up question.
3 The conversationId captured from RUN_STARTED.threadId in the head answer stream.
4 The conversationToken captured from the header custom event in the head answer stream.

Response format

The follow-up response uses the same AG-UI event stream format as the head answer. Your client can reuse the same stream-parsing logic described in the Parse the event stream section. The events, their order, and their payloads are identical.

Follow-up not supported

When an agent has followUpEnabled set to false, follow-up requests are rejected. The backend returns a RUN_ERROR event with the code KNOWLEDGE:SSE_FOLLOW_UP_NOT_SUPPORTED.

Your client should check the followUpEnabled field in the header custom event during the head answer stream. If the value is false, don’t offer a follow-up input to the user and don’t send follow-up requests.

Analytics and event tracking

For analytics dashboards and machine learning models to work correctly, your client must log usage analytics events to the Usage Analytics Write API. The Search Agent doesn’t log these events on your behalf. Your client is responsible for sending them.

There are three types of events to log:

Search events

Log a search event when the user submits a query that triggers answer generation. This is the event that feeds Coveo ML models to improve relevance over time.

The search event must include the searchQueryUid that identifies the query, and should be sent before or alongside the answer generation request.

Click events

Log a click event when the user clicks a citation link in the head answer. Clicks on citations for the first answer are tracked as standard click events, which means they feed ML models.

Note

Clicks on citations for follow-up answers are tracked as custom events (not click events), so they don’t feed ML models directly.

Custom events

Log custom events for answer lifecycle and user interactions. These events power reporting dashboards but don’t feed ML models.

The following table lists the key custom events to log:

eventValue: When to log

generatedAnswerStreamEnd

After the answer finishes streaming. Include generativeQuestionAnsweringId (the runId), answerGenerated (Boolean), and conversationId (the threadId) in customData.

likeGeneratedAnswer

User clicked thumbs up.

dislikeGeneratedAnswer

User clicked thumbs down.

openGeneratedAnswerSource

User clicked a citation link (for follow-up answer citations).

generatedAnswerSourceHover

User hovered a citation. Include citationHoverTimeMs in customData.

generatedAnswerCopyToClipboard

User copied the answer.

All custom events use eventType: "generatedAnswer" and include generativeQuestionAnsweringId in customData.

For the full list of events and how to set up dashboards, see Search Agent reports and UA events.

About the analytics object in the request body

The head answer and follow-up endpoints accept an optional analytics object. For production implementations, set capture to false and rely on client-side event logging as described in the previous section.

Nonetheless, even when capture is set to false, including user_id in the analytics object is recommended to unlock advanced reporting in Snowflake reports.

Note

The analytics.capture field is meant for Coveo’s Event Protocol, which isn’t yet available for Knowledge use cases. Setting it to true isn’t required when you log search, click, and custom events client-side.

Error handling

Errors can occur at two levels when interacting with the Agent API: at the transport level (before streaming begins) or at the stream level (during an active SSE stream). Your client should handle both categories.

Transport-level errors

Transport-level errors occur before the SSE stream is established. These are standard HTTP failures, meaning that the server never begins streaming events.

Common transport-level errors include:

  • Network error: Connection refused, DNS resolution failure, or request timeout. The client never receives a response.

  • Non-2xx HTTP status: The server responds with an error status code before streaming begins:

    • 401 Unauthorized: The access token is missing, expired, or invalid.

    • 403 Forbidden: The token lacks the required privileges.

    • 404 Not Found: The agent ID or endpoint path is incorrect.

For transport-level errors, your client should:

  1. Check the HTTP status code (if available) or detect the network failure.

  2. Display an appropriate error message to the user.

  3. Allow the user to retry the request.

Stream-level errors

Stream-level errors occur during an active SSE stream. The server sends a 200 RUN_ERROR event with a code and a human-readable message:

{"type": "RUN_ERROR", "code": "KNOWLEDGE:SSE_INTERNAL_ERROR", "message": "An internal error occurred"}

When your client receives a RUN_ERROR event, it should stop processing the stream, interpret the code to determine what happened, and take the appropriate action.

Error codes

The following table lists the most common error codes, their meanings, and the recommended client behavior.

Code Meaning Recommended client behavior

KNOWLEDGE:SSE_MAX_DURATION_EXCEEDED

The SSE stream exceeded the maximum allowed duration.

Show an error message and allow the user to retry.

KNOWLEDGE:SSE_FOLLOW_UP_NOT_SUPPORTED

A follow-up was attempted on an agent that doesn’t support it.

Hide the follow-up input. This error shouldn’t normally appear if followUpEnabled was checked in the header custom event.

KNOWLEDGE:NOT_FOUND

The conversation ID is invalid or expired.

Show an error on the follow-up turn and suggest starting a new conversation.

KNOWLEDGE:SSE_MODELS_NOT_AVAILABLE

The underlying ML models are temporarily unavailable.

Show an error message and allow the user to retry.

KNOWLEDGE:SSE_INTERNAL_ERROR

An internal server error occurred.

Show a generic error message and allow the user to retry.

KNOWLEDGE:SSE_TURN_LIMIT_REACHED

The maximum number of conversation turns has been reached.

Show a message indicating the conversation limit is reached and disable the follow-up input.

No answer generated

A stream that completes normally with a RUN_FINISHED event but where completionReason isn’t ANSWERED isn’t an error. The agent couldn’t produce an answer for the given query.

This can happen when:

  • No relevant content exists in the index for the question.

  • Query filters (aq, cq, or facets) exclude all matching results.

  • User permissions hide the content that would have been relevant.

  • The query is empty or too ambiguous for the agent to formulate an answer.

  • The question is blocked by the agent guardrails (for example, questions about malicious or harmful topics).

In this scenario, your client should:

  1. Check result.completionReason in the RUN_FINISHED event.

  2. If the value isn’t ANSWERED, display a "no answer available" message instead of the (empty) accumulated text.

  3. Don’t show an error indicator. The request succeeded, but the agent chose not to answer.

{"type": "RUN_FINISHED", "result": {"completionReason": "NO_ANSWER"}}

API reference

For the full request and response schemas, see the Knowledge Generative AI API reference.