Skip to main content

Choosing an endpoint

Looking for the product view?
The Help Center explains this surface as users see it: Chatting with Informer.

Informer offers four ways to send a prompt and get an answer back. They differ on three things: whether the response arrives as a stream, whether an Assistant's configuration is applied, and whether the conversation is saved. Answer those three questions and the choice is made for you.

The four endpoints​

EndpointResponseAssistant configurationConversation saved
POST /api/assistants/{id}/_askPlain JSONYes, the Assistant's ownNo
POST /api/chats/{id}SSE streamYes, from the stored chatYes
POST /api/models/{id}/_chatSSE streamYes, supplied per requestNo
POST /api/models/{id}/_completionSSE streamNoNo
POST /api/models/{id}/_objectPlain JSONNoNo

_ask and _object are the two that do not stream. The others return text/event-stream, so the caller has to read the stream and assemble the text before it has an answer.

The difference between the two plain-JSON routes is what they carry: _ask addresses an Assistant and applies its whole configuration, while _object addresses a model and takes a JSON Schema. If you want an answer from an Assistant without handling a stream, _ask is the one.

Start here: three questions​

1. Do you need the Assistant to behave like an Assistant?​

An Assistant is not just a system prompt. When one is attached, the request also picks up its skills (Website Search, for example), its Toolkits, its attached Datasets, its knowledge files, its stored secrets, and any sub-Assistants it dispatches to. That whole bundle is assembled from the Assistant's own configuration, not from your payload.

_ask, /api/chats/{id} and _chat can all do this. _completion and _object call the model directly and carry the model's own instructions, so an Assistant's skills never run on them. If your Assistant exists because of the work its skills do, those two endpoints will answer confidently while quietly doing none of it.

2. Should the conversation be saved?​

_chat runs entirely in memory. Nothing is written, nothing is billed to a chat history, and the next request starts from whatever messages you send it. That is the right shape for a stateless integration such as a search box that answers one question at a time.

If a person should be able to close the page and come back to what they were saying, you want a saved chat. See Saving a conversation for a signed-in user below.

3. Can you consume a stream?​

If the answer is no, use _ask. It answers with a plain JSON body and still applies the Assistant's full configuration, so unlike _object it costs you nothing but the ability to render tokens as they arrive.

Reach for _object only when you need output shaped by a JSON Schema, and accept that no Assistant is applied.

Running an Assistant without saving anything​

_chat takes the Assistant id in the payload and applies its full configuration for that one request:

{
"messages": [
{ "role": "user", "parts": [{ "type": "text", "text": "Which programs have fall deadlines?" }] }
],
"primaryAssistantId": "admin:admissions-helper"
}

primaryAssistantId is the Assistant that answers. assistantIds is a different thing: it offers a set of Assistants the model may dispatch to. Supplying exactly one assistantIds entry and no primaryAssistantId promotes it to the primary Assistant, so the two forms converge in that one case.

Both resolve through read_access, so an id the caller cannot see is treated as absent rather than rejected. An Assistant that answers with no configuration at all is the symptom to look for.

Saving a conversation for a signed-in user​

Set ownerAssistantId when creating the chat. That marks the chat as this user's chat with that Assistant, which is what makes it findable again later: chats are per-user, so each person gets their own.

  1. Look for an existing chat

    GET /api/assistants/{id} returns a chatId when the current user already has a chat owned by that Assistant. If it is there, skip to sending the message.

  2. Create one if there is none

    POST /api/chats with { "modelId": "...", "ownerAssistantId": "..." }. This route only writes rows, so it does not stream and does not call a provider.

  3. Send the message

    POST /api/chats/{chatId} with the message. This one streams.

Only step three calls a model. The lookup in step one is what makes the conversation resume, so run it on every load rather than caching the id and assuming it stays valid.

Two things that catch integrators out​

Messages carry parts, not content​

A message is an object with a role and an array of parts:

{ "role": "user", "parts": [{ "type": "text", "text": "Hello" }] }

This is the format across /api/chats/{id}, _chat, and _object. A message with a plain content string will not be read as text.

Unknown fields are dropped, not rejected​

Every one of these routes validates with stripUnknown, so a field that is not in the payload table is removed before the handler runs. There is no 400 and no warning. A misspelled primaryAssistantId, or an assistantIds sent to _object (which has no such field), produces a perfectly plausible answer with the Assistant silently absent.

When a response looks like the model answered from general knowledge, suspect a dropped field before suspecting the model.

Structured output​

_object takes a JSON Schema and returns an object matching it as the entire response body, with no envelope:

{
"schema": {
"type": "object",
"properties": {
"programs": { "type": "array", "items": { "type": "string" } },
"deadline": { "type": "string" }
},
"required": ["programs"]
},
"messages": [
{ "role": "user", "parts": [{ "type": "text", "text": "Which programs have fall deadlines?" }] }
]
}

Its payload accepts schema, messages, and outputSize. There is no field for an Assistant, a system prompt, or Toolkits.

Payload limits​

POST /api/chats/{id} and the _explain route raise their payload ceiling to 200MB so that large conversations and attachments fit. The three /api/models/ routes do not, so they inherit the server default of 1MB. A long message history posted to _chat can exceed that where the same history posted to a saved chat would not.

Troubleshooting​

  • The Assistant answers but its skills never run. Check which endpoint you are calling. _completion and _object cannot carry an Assistant at all, and on _chat a misspelled or unreadable primaryAssistantId is dropped silently.
  • Every request starts a new conversation. The chat was created without ownerAssistantId, so GET /api/assistants/{id} has nothing to hand back as chatId. Check the create payload rather than the lookup.
  • A 402 before anything streams. Credit enforcement runs in the pre-block, ahead of the model call. The code field names which limit was hit. See the enforcement flow.
  • A 413 on _chat that does not happen on a saved chat. The 1MB default ceiling, described under Payload limits.
  • The response is empty or unparsed. Three of the four endpoints return SSE. Confirm you are reading text/event-stream rather than parsing the body as JSON.
  • A hand-written stream parser throws on a line that isn't JSON. Lines that start with : are keepalive comments the server sends every 15 seconds; skip them. See Streaming Responses.
  • Chats for the full chat lifecycle and every field on each payload
  • Models for the three /api/models/{id}/ routes in detail
  • Assistants for Assistant configuration and the chatId behavior on lookup