Docs · API nativa
Chat stream
Os endpoints de chat nativos são os que os apps do ShvIA usam. Em comparação com os endpoints /v1, eles guardam as suas conversas no servidor e aceitam um esforço de raciocínio. POST /api/v1/chat também aceita anexos de arquivo e pode usar os seus combos. Eles têm ainda convenções próprias, descritas aqui.
| Endpoint | Estilo | Guarda a conversa |
|---|---|---|
POST /api/v1/chat | server-sent events | sim |
POST /api/v1/agent/chat | uma resposta JSON | sim |
POST/api/v1/chat#
Devolve a resposta em streaming, como server-sent events.
- Auth: a sua chave de API.
- Limite: 60 requisições por minuto por conta por padrão. Veja limites e cotas.
- Corpo: JSON, ou
multipart/form-dataquando você envia arquivos.
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"
}'Requisição#
| Campo | Tipo | Notas |
|---|---|---|
message | string | o turno do usuário. prompt é aceito como apelido. No máximo 32000 caracteres por padrão. Uma mensagem vazia é respondida com um evento de erro |
profile_name | string | o id do modelo, model@server, de modelos. profile é aceito como apelido |
model, server | string | um nome de modelo simples e a chave de um servidor, como alternativa a profile_name. model não entende o @: ponha o id composto em profile_name |
session_id | string | a chave que continua uma conversa, veja abaixo. No máximo 191 caracteres |
effort | string | low, medium, high, xhigh ou max. Qualquer outro valor é ignorado. Um valor válido é salvo na conversa |
combo_id | integer | use um dos seus combos em vez de escolher um modelo. Um id desconhecido é ignorado |
files[] | file | anexos, só com multipart/form-data |
Quando nenhum modelo é informado, o gateway usa o seu padrão salvo e, depois, o padrão da instalação. Não existe temperature, nem max_tokens, nem o campo system, nem como enviar uma lista de mensagens: a conversa é a que está guardada no servidor, e o servidor acrescenta as próprias instruções a ela.
Com um único modelo, este endpoint não recorre ao seu modelo padrão como /v1 e agent/chat fazem: um modelo sem chave por trás, ou que falha, termina num evento error. Dê a ele outros modelos para tentar com um combo.
Anexos#
- Arquivos de texto e de código: até 10 por mensagem, 262144 bytes cada e 1048576 bytes no total.
- Imagens: PNG, JPEG, WebP ou GIF, até 8 por mensagem e 20 MiB cada. Envie só para um modelo que enxerga imagens, ou a requisição é recusada.
- Pacotes (zip, tar, tgz) são abertos e os arquivos de texto dentro deles são usados.
- O grupo da sua conta define mais um limite para o número de anexos por mensagem.
Conversas e session_id#
Com uma chave de API, toda chamada a este endpoint pertence a uma conversa num projeto chamado API. Se a chamada continua uma conversa anterior ou não, isso depende de session_id:
- Com
session_id: a conversa mais recente sua com esse mesmo valor continua, e os turnos anteriores são enviados ao modelo, quantos couberem na janela de contexto dele: os mais antigos podem ser resumidos ou deixados de fora. Um valor novo começa uma conversa nova. - Sem ele: toda chamada é uma conversa nova com um turno.
O stream nunca diz o id da conversa. Para lê-lo depois, liste as suas conversas e compare o campo session_id. Veja conversas. Você não pode enviar um conversation_id a este endpoint; para continuar uma conversa pelo id, use POST /api/v1/agent/chat.
O stream#
A resposta é text/event-stream. Cada evento é uma linha, data: {"kind": "…", "data": {…}}, seguida de uma linha em branco. Não há linhas event: nem id:, e nenhum comentário de keep-alive. Como a requisição é um POST com headers, o EventSource de um navegador não pode ser usado: leia o stream do corpo. Ignore qualquer kind que você não conheça, já que novos são acrescentados com o tempo. Tire a resposta final de done. Se a instalação deixa o chat usar ferramentas, o stream também pode trazer tool_call, tool_result e eventos parecidos, e um evento text_clear, que quer dizer que o texto mostrado até agora deve ser descartado.
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."}}kind | data | Quando |
|---|---|---|
user_message | {"id"} | o primeiro, quando a sua mensagem foi salva |
model | o modelo resolvido: name, model, server, target_source, request_id, e mais alguns campos que servem aos apps | antes do primeiro token, e de novo se outro modelo assume depois de uma falha |
reasoning_delta | {"delta"} | o modelo envia o raciocínio em streaming |
text_delta | {"delta"} | cada fragmento da resposta |
warning | {"codigo": "resposta_nao_salva", "correlation_id"} | a resposta chegou até você, mas não pôde ser salva. Não é um erro: guarde o texto |
warn | {"kind": "guard.repetition", "message"} | uma guarda notou algo na resposta, como um laço repetitivo. A resposta continua sendo entregue e salva |
citations | {"message_id", "citations"} | quando a resposta usou documentos de uma base de conhecimento |
error | {"message", "codigo", "status"} | algo falhou. codigo e status só vêm quando são conhecidos |
usage | tokens, tempos, o effort realmente aplicado e cost | depois da resposta, antes de done |
done | {"text"}, que vem vazio depois de um erro que termina sem texto, e [] depois de uma recusa antecipada | o último. Sempre chega |
cost é o custo que o provedor informou, em USD, ou null. cost_estimated é o número de tokens vezes a tabela de preços, ou null. usage.effort é o nível de esforço que o modelo realmente aplicou, ou null. Os tempos são números que podem ter casas decimais, então leia-os como float. Atenção: um data vazio é escrito como [], não como {}.
Códigos de erro#
Quando um evento error tem codigo, decida por ele. message é para pessoas e pode estar em outro idioma.
codigo | Significado |
|---|---|
mensagem_vazia | a mensagem estava vazia |
mensagem_muito_longa | a mensagem passa do limite de tamanho |
roteamento_sem_alvo | o modelo ou o servidor não pôde ser resolvido. A mensagem pode listar alternativas |
bot_desativado | a conversa que a conta tem aberta no app web pertence a um bot que está desligado |
modelo_sem_visao | você enviou imagens a um modelo que não as enxerga |
anexo_invalido | um anexo não pode ser lido, é grande demais ou não é de um tipo aceito |
pacote_sem_texto | um pacote não tinha nenhum arquivo de texto aproveitável |
sessao_id_muito_longa | session_id passa de 191 caracteres |
combo_sem_modelo | o combo não tem nenhum modelo alcançável |
limite_perfil | os limites do seu grupo recusaram a chamada: o modelo não é permitido, ou um teto diário, semanal ou mensal de tokens foi atingido |
resposta_vazia | o modelo respondeu com nada |
limite_de_requisicoes | o provedor está limitando as requisições. O limite de requisições do próprio gateway é um HTTP 429, não um evento |
provedor_recusou_pedido | o provedor rejeitou a requisição, com status. Outro modelo não ajuda |
provedor_indisponivel | o provedor falhou, ou não tem uma chave sua, com status |
shvia_fora_do_ar | os servidores do operador não puderam ser alcançados |
Um stream cortado por timeout envia um error sem codigo, depois usage, depois done com o texto parcial, que é salvo. Uma resposta com um laço repetitivo é entregue e salva com um evento warn a mais.
Mais uma coisa a saber: se a conta tem aberta no app web uma conversa com um bot, o modelo ao qual o bot está fixado vale mais que o que você pediu. Leia o evento model para ver o que rodou.
POST/api/v1/agent/chat#
Uma chamada que bloqueia, uma resposta JSON.
- Auth: a sua chave de API. Uma chave cujo grupo de conta não tem acesso à API é recusada com
401. - Limite: 20 requisições por minuto por conta por padrão, no contador que os outros endpoints limitados dividem. Veja limites e cotas.
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"'" }'{
"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
}Requisição#
| Campo | Notas |
|---|---|
message | obrigatório, no máximo 32000 caracteres |
profile_name | o id do modelo, model@server. model e server também funcionam como campos separados |
conversation_id | um inteiro. Continua uma das suas conversas. Sem ele, toda chamada cria uma nova |
effort | low, medium, high, xhigh ou max. Diferente do endpoint de stream, um valor inválido é um 422 |
Como os endpoints /v1, este monta uma cadeia curta: o modelo que você pediu e depois o modelo padrão da sua conta. Um modelo de nuvem sem chave por trás, ou que falha com algo que outro modelo pode resolver antes de enviar qualquer texto, é substituído pelo modelo padrão, e a resposta nomeia o modelo que respondeu: leia model, server e profile_name. Veja modelos.
Para manter uma conversa de vários turnos, devolva o conversation_id da resposta anterior. Aqui não há session_id, nem anexos, nem streaming. timing.queue_wait_ms é sempre 0.
Erros#
Este endpoint responde com status HTTP e um texto detail, e sem codigo:
| Status | Corpo | Quando |
|---|---|---|
| 401 | {"message": "Unauthenticated."} | a credencial não é válida |
| 400 | {"detail": "…"} | o modelo ou o servidor é desconhecido, ou o provedor rejeitou a requisição como inválida. Nesse segundo caso o status é o do próprio provedor, em geral 400, e o corpo também traz request_id |
| 403 | {"detail": "…"} | os limites do seu grupo recusaram a chamada: um modelo não permitido, uma cota esgotada, ou servidores remotos que o grupo não pode usar |
| 404 | {"detail": "…"} | o conversation_id não é seu |
| 422 | {"message", "errors": {…}} | o corpo falhou na validação |
| 502 | {"detail": "…", "request_id": "…"} | o modelo respondeu com nada, ou falhou de um jeito que não vale tentar de novo |
| 503 | {"detail": "…", "retry_after": 30, "request_id": "…"} e um header Retry-After | todos os modelos da cadeia falharam de um jeito que vale tentar de novo: um limite de requisições, uma cota esgotada no provedor, um provedor sobrecarregado, uma chave de provedor recusada ou ausente, um erro de servidor, ou um servidor que não pode ser alcançado. Espere retry_after segundos. Não quer dizer que o gateway está fora do ar, e uma chave de provedor recusada não é a sua chave do gateway |
| 504 | {"detail": "…", "request_id": "…", "partial": {…}} e X-Inference-Incomplete: read_timeout | o modelo parou no meio da resposta. A resposta parcial é salva |
Um 403 aqui não é limite de requisições: não tente de novo sem mudar alguma coisa.