ShvIA

Docs · Native API

Chat stream

The native chat endpoints are what the ShvIA apps use. Compared with the /v1 endpoints they keep your conversations on the server and accept a reasoning effort. POST /api/v1/chat also accepts file attachments and can use your combos. They also have their own conventions, described here.

EndpointStyleStores the conversation
POST /api/v1/chatserver-sent eventsyes
POST /api/v1/agent/chatone JSON answeryes

POST/api/v1/chat#

Streams the answer as server-sent events.

  • Auth: your API key.
  • Limit: 60 requests per minute per account by default. See rate limits and quotas.
  • Body: JSON, or multipart/form-data when you send files.
bash
curl -sN "$SHVIA_URL/api/v1/chat" \
  -H "Authorization: Bearer $SHVIA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "message": "Say hello in one short sentence.",
    "profile_name": "'"$SHVIA_MODEL"'",
    "session_id": "support-ticket-1042"
  }'

Request#

FieldTypeNotes
messagestringthe user turn. prompt is accepted as an alias. At most 32000 characters by default. An empty message is answered with an error event
profile_namestringthe model id, model@server, from models. profile is accepted as an alias
model, serverstringa bare model name and a server key, as an alternative to profile_name. model does not understand the @: put the composite id in profile_name
session_idstringthe key that continues a conversation, see below. At most 191 characters
effortstringlow, medium, high, xhigh or max. Any other value is ignored. A valid value is saved on the conversation
combo_idintegeruse one of your combos instead of choosing a model. An unknown id is ignored
files[]fileattachments, multipart/form-data only

When no model is given, the gateway uses your saved default, then the installation's default. There is no temperature, no max_tokens, no system field and no way to send a messages array: the conversation is the one stored on the server, and the server adds its own instructions to it.

With a single model, this endpoint does not fall back to your default model the way /v1 and agent/chat do: a model with no key behind it, or one that fails, ends in an error event. Give it other models to try with a combo.

Attachments#

  • Text and code files: up to 10 per message, 262144 bytes each and 1048576 bytes in total.
  • Images: PNG, JPEG, WebP or GIF, up to 8 per message and 20 MiB each. Send them only to a model that can see images, or the request is refused.
  • Archives (zip, tar, tgz) are opened and their text files used.
  • Your account group sets a further limit on the number of attachments per message.

Conversations and session_id#

With an API key, every call to this endpoint belongs to a conversation in a project named API. Whether a call continues an earlier conversation depends on session_id:

  • With session_id: the most recent conversation of yours with that same value is continued, and the earlier turns are sent to the model, as many as fit its context window: older ones can be summarized or left out. A new value starts a new conversation.
  • Without it: every call is a new conversation with one turn.

The stream never tells you the id of the conversation. To read it back, list your conversations and match the session_id field. See conversations. You cannot send a conversation_id to this endpoint; to continue a conversation by its id, use POST /api/v1/agent/chat.

The stream#

The reply is text/event-stream. Each event is one line, data: {"kind": "…", "data": {…}}, followed by a blank line. There are no event: or id: lines and no keep-alive comments. Because the request is a POST with headers, a browser EventSource cannot be used: read the body stream instead. Ignore any kind you do not know, since new ones are added over time. Take the final answer from done. If the installation lets the chat call tools, the stream can also carry tool_call, tool_result and similar events, and a text_clear event, which means that the text shown so far is to be discarded.

sse
data: {"kind":"user_message","data":{"id":5121}}

data: {"kind":"model","data":{"name":"claude-sonnet-4-6@anthropic","model":"claude-sonnet-4-6","profile":"claude-sonnet-4-6@anthropic","server":"anthropic","server_label":"…","target_source":"request","request_id":"3b1a8f7e-…"}}

data: {"kind":"text_delta","data":{"delta":"Hello"}}

data: {"kind":"text_delta","data":{"delta":"! Nice to meet you."}}

data: {"kind":"usage","data":{"model":"claude-sonnet-4-6","profile":"claude-sonnet-4-6@anthropic","server":"anthropic","server_label":"…","prompt_tokens":42,"completion_tokens":8,"total_tokens":50,"thinking_time_ms":null,"response_time_ms":890.4,"reasoning_tokens":null,"effort":"medium","cost":null,"cost_estimated":0.000246}}

data: {"kind":"done","data":{"text":"Hello! Nice to meet you."}}
kinddataWhen
user_message{"id"}first, once your message is saved
modelthe resolved model: name, model, server, target_source, request_id, and a few more fields that serve the appsbefore the first token, and again if another model takes over after a failure
reasoning_delta{"delta"}the model streams its reasoning
text_delta{"delta"}each fragment of the answer
warning{"codigo": "resposta_nao_salva", "correlation_id"}the answer reached you but could not be saved. It is not an error: keep the text
warn{"kind": "guard.repetition", "message"}a guard noticed something about the answer, such as a repeating loop. The answer is still delivered and saved
citations{"message_id", "citations"}when the answer used documents from a knowledge base
error{"message", "codigo", "status"}something failed. codigo and status are present only when known
usagetokens, timing, effort actually applied, and costafter the answer, before done
done{"text"}, which is empty after an error that ends in no text, and [] after an early refusallast. It always arrives

cost is the cost the provider reported, in USD, or null. cost_estimated is tokens times the price table, or null. usage.effort is the effort level the model really applied, or null. The times are numbers that may have decimals, so parse them as floats. Note that an empty data is written as [], not {}.

Error codes#

When an error event has a codigo, switch on it. message is for people and may be in another language.

codigoMeaning
mensagem_vaziathe message was empty
mensagem_muito_longathe message is over the length limit
roteamento_sem_alvothe model or server cannot be resolved. The message can list alternatives
bot_desativadothe conversation that the account has open in the web app belongs to a bot that is switched off
modelo_sem_visaoyou sent images to a model that cannot see them
anexo_invalidoan attachment cannot be read, is too large, or is not a supported type
pacote_sem_textoan archive held no usable text file
sessao_id_muito_longasession_id is over 191 characters
combo_sem_modelothe combo has no reachable model
limite_perfilyour group's limits refused the call: the model is not allowed, or a daily, weekly or monthly token ceiling is reached
resposta_vaziathe model answered with nothing
limite_de_requisicoesthe provider is rate limiting. The request limit of the gateway itself is an HTTP 429, not an event
provedor_recusou_pedidothe provider rejected the request, with status. Another model will not help
provedor_indisponivelthe provider failed, or has no key from you, with status
shvia_fora_do_arthe operator's servers could not be reached

A stream cut by a timeout sends an error with no codigo, then usage, then done with the partial text, which is saved. An answer with a repeating loop is delivered and saved with an extra warn event.

One more thing to know: if the account has a conversation with a bot open in the web app, the model that the bot is pinned to wins over the one you asked for. Read the model event to see what ran.

POST/api/v1/agent/chat#

One blocking call, one JSON answer.

  • Auth: your API key. A key whose account group has no API access is refused with 401.
  • Limit: 20 requests per minute per account by default, on the counter that the other limited endpoints share. See rate limits and quotas.
bash
curl -s "$SHVIA_URL/api/v1/agent/chat" \
  -H "Authorization: Bearer $SHVIA_API_KEY" -H "Content-Type: application/json" \
  -d '{ "message": "Say hello in one short sentence.", "profile_name": "'"$SHVIA_MODEL"'" }'
json
{
  "request_id": "9eaa5ed8-e7fa-423b-9a93-d9ff3d4c8a03",
  "response": "Hello! Nice to meet you.",
  "model": "claude-sonnet-4-6",
  "server": "anthropic",
  "server_label": "…",
  "profile_name": "claude-sonnet-4-6@anthropic",
  "target_source": "request",
  "effort": null,
  "tokens": { "prompt": 42, "completion": 8, "total": 50 },
  "timing": { "queue_wait_ms": 0, "response_time_ms": 890 },
  "conversation_id": 5
}

Request#

FieldNotes
messagerequired, at most 32000 characters
profile_namethe model id, model@server. model and server also work as separate fields
conversation_idan integer. Continues one of your own conversations. Without it, every call creates a new one
effortlow, medium, high, xhigh or max. Unlike the stream endpoint, an invalid value is a 422

Like the /v1 endpoints, this one builds a short chain: the model you asked for, then your account's default model. A cloud model with no key behind it, or one that fails with something another model can fix before it sends any text, is replaced by the default model, and the reply names the model that answered: read model, server and profile_name. See models.

To hold a multi-turn conversation, send back the conversation_id of the previous answer. There is no session_id here, no attachments and no streaming. timing.queue_wait_ms is always 0.

Errors#

This endpoint answers with HTTP statuses and a detail text, and no codigo:

StatusBodyWhen
401{"message": "Unauthenticated."}the credential is not valid
400{"detail": "…"}the model or server is unknown, or the provider rejected the request as invalid. In that second case the status is the provider's own, usually 400, and the body also carries request_id
403{"detail": "…"}your group's limits refused the call: a model that is not allowed, a quota that is used up, or remote servers that the group may not use
404{"detail": "…"}the conversation_id is not yours
422{"message", "errors": {…}}the body failed validation
502{"detail": "…", "request_id": "…"}the model answered with nothing, or failed in a way that is not worth retrying
503{"detail": "…", "retry_after": 30, "request_id": "…"} and a Retry-After headerevery model of the chain failed in a way that is worth retrying: a rate limit, a used-up provider quota, an overloaded provider, a refused or missing provider key, a server error, or a server that cannot be reached. Wait retry_after seconds. It does not mean that the gateway is down, and a refused provider key is not your gateway key
504{"detail": "…", "request_id": "…", "partial": {…}} and X-Inference-Incomplete: read_timeoutthe model stopped mid-answer. The partial answer is saved

A 403 here is not a rate limit: do not retry it without changing something.