GaurGaur docs

Chat

Ask questions in natural language over a consumer's contracts with the OpenAI-compatible chat API.

The chat completions API lets your application ask questions in plain language. Gaur's agent interprets the question, answers it using the consumer's contracts, and streams back a response. It's the right choice for conversational features and assistants.

The endpoint

POST /v1/api/{consumer_slug}/chat/completions
Authorization: Bearer <api_key>
Content-Type: application/json

The endpoint is OpenAI-compatible. The request and the streamed response follow the chat-completions shape, so OpenAI-style client code can talk to it with little change. There's an important catch about response fields, covered in Reading the full response below. Read that section before you build on this endpoint.

The request

{
  "model": "gaur-nlq-v1",
  "messages": [
    { "role": "user", "content": "Show me weekly revenue trends." }
  ]
}
FieldPurpose
modelThe model identifier for the request.
messagesThe conversation so far, an ordered list of role/content messages.

Each message has a role (user, assistant, or system) and content. To continue a conversation, send the prior messages back along with the new user message.

The response

The response is a server-sent events (SSE) stream with content type text/event-stream. Each data: line is a JSON chunk shaped like an OpenAI chat completion chunk, with a choices array carrying a delta. The stream ends with a final chunk whose finish_reason is stop, followed by a data: [DONE] line.

HTTP/1.1 200 OK
Content-Type: text/event-stream

data: {"choices":[{"delta":{"role":"assistant"}}]}

data: {"choices":[{"delta":{"content":"Weekly "}}]}

data: {"choices":[{"delta":{"content":"revenue "}}]}

...

data: {"choices":[{"delta":{},"finish_reason":"stop"}]}

data: [DONE]

Reading the full response

This is the part to get right. Gaur's agent does more than write prose. It runs queries, produces result rows, and suggests charts. The chat endpoint delivers all of that, but only the natural-language text travels in the standard delta.content field. Everything else rides in an extra field.

What's in delta.content

choices[0].delta.content carries the agent's narrative: the sentences it writes to explain its answer. A plain OpenAI client that concatenates delta.content will render a readable reply.

But that reply is just the agent's commentary. The actual query results and chart suggestions are not in delta.content.

What's in delta.gaur_event

Gaur attaches its richer output to a non-standard field on the delta: choices[0].delta.gaur_event. Each gaur_event is an object with a type:

gaur_event typeWhat it carries
thinkingThe agent's progress and reasoning steps.
assistantA chunk of the narrative text (this also becomes delta.content).
dataThe query result: the actual rows and columns.
chart_suggestionA suggested chart for the result data.
context_stateThe objects and query the agent resolved for this turn.
questionnaireA set of clarifying questions when the agent needs more input.
errorAn error that occurred during the turn.

If you read only delta.content, you get the agent's narrative and nothing else. No result rows, no chart suggestions. To get the data your application actually needs to render, you have to read delta.gaur_event.

The catch with OpenAI SDKs

gaur_event is not part of the OpenAI schema. A strict OpenAI client library deserializes each chunk into a typed object and silently drops unknown fields. With that kind of client, delta.gaur_event never reaches your code, and you're left with only the narrative text.

To get the extra fields, do one of the following:

  • Parse the stream yourself. Read each SSE data: line, JSON.parse it, and look at choices[0].delta.gaur_event. This is the most reliable approach and what we recommend.
  • Use your SDK's "extra fields" accessor. Some SDKs preserve unknown fields, for example through a model_extra (Python) or a raw-response accessor. Use that to reach gaur_event.

Either way, the rule is the same: don't rely on the typed delta.content alone. Read the raw delta.gaur_event to get results and chart suggestions.

Handling the stream

const res = await fetch(url, { method: "POST", headers, body });
const reader = res.body.getReader();
const decoder = new TextDecoder();
let buffer = "";

while (true) {
  const { value, done } = await reader.read();
  if (done) break;
  buffer += decoder.decode(value, { stream: true });

  const lines = buffer.split("\n");
  buffer = lines.pop() ?? "";

  for (const line of lines) {
    if (!line.startsWith("data: ")) continue;
    const payload = line.slice(6);
    if (payload === "[DONE]") continue;

    const chunk = JSON.parse(payload);
    const delta = chunk.choices?.[0]?.delta ?? {};

    if (delta.content) {
      appendNarrative(delta.content);
    }
    if (delta.gaur_event) {
      handleGaurEvent(delta.gaur_event); // data, chart_suggestion, etc.
    }
  }
}

A typical handleGaurEvent switches on gaur_event.type: append thinking to a progress indicator, render the rows from a data event, and offer the chart from a chart_suggestion event.

How chat stays governed

The chat endpoint doesn't hand callers free rein over your data. It answers within the boundaries the consumer defines:

  • It can only use the contracts the consumer is scoped to.
  • The consumer's system prompt shapes tone and domain framing.
  • Contract row-level security still applies to any data the answer is built from.
  • The consumer's query mode decides how freely the agent can generate.

Query mode: bounded vs. unbounded generation

The query mode is the setting to think hardest about, because it decides whether the AI's generation is bounded.

  • Contract-only mode is bounded. The agent can only choose a published contract and fill in a query spec that Gaur validates. Every answer is shaped by a contract a builder authored and verified, so what the AI can produce is a fixed, known set. This is the safety guarantee to rely on for customer-facing analytical chatbots and embedded in-product chat, where you want to know exactly what analytics an end user can reach.
  • SQL-only and contract-with-SQL-fallback are unbounded. In these modes the agent writes SQL, so it composes queries freely and generation is open-ended again. It is still safe in terms of access: Gaur restricts the generated SQL to the objects the consumer is scoped to, never your whole database, and the SQL is read-only and validated before it runs. But the shape of a query is no longer bounded by a contract, so these modes suit internal or exploratory assistants more than untrusted end-user surfaces.

Building a chatbot into a product your customers use? Put it on a contract-only consumer. Bounded generation means the bot can only answer with analytics you have explicitly published and validated.

The quality of answers also depends heavily on the context builders attach to collections and contracts. Good descriptions of what the data means produce better answers.

Chat vs. the REST query API

Use chat when

The input is a natural-language question, the shape of the answer isn't known in advance, or you're building a conversational experience.

Use the query API when

You need an exact, repeatable result with a known shape: a dashboard tile, a report, a programmatic read. See Querying contracts.

Next

On this page