Skip to navigation
API

Completamenti chat

L’endpoint compatibile con OpenAI Chat Completions: richiesta, risposta, streaming, strumenti, errori

View as Markdown

POST /chat/completions è l’endpoint che genera le risposte. Gli mandi una lista di messaggi, ti restituisce il messaggio successivo. Richiesta e risposta hanno la forma di OpenAI Chat Completions, così puoi usare l’SDK ufficiale cambiando solo indirizzo di base e chiave.

Serve una chiave con il permesso Completion: le chiavi personali ce l’hanno sempre. → Autenticazione e chiavi

La richiesta

CampoObbligatorioCosa fa
modelSìL’id del modello, nella forma fornitore/modello. Senza, ricevi 400
messagesSìLa conversazione, in ordine. Almeno un elemento
streamNotrue per ricevere la risposta a pezzi, man mano che viene scritta
temperatureNoDa 0 a 2
top_pNoDa 0 a 1
max_tokens o max_completion_tokensNoTetto alla lunghezza della risposta. Senza, il tetto è 16.384 token
stopNoUna stringa, o da 1 a 4 stringhe, che fermano la generazione
presence_penalty, frequency_penaltyNoDa -2 a 2
seedNoUn intero, per risposte più ripetibili dove il modello lo supporta
tools, tool_choiceNoLe funzioni che il modello può chiederti di eseguire, al massimo 128
response_formatNotext, json_object o json_schema
reasoning_effortNonone, minimal, low, medium, high o xhigh, per i modelli che ragionano
userNoUn tuo identificativo dell’utente finale

Ogni messaggio ha un role fra system, developer, user, assistant e tool. Il content è una stringa oppure una lista di parti (testo, immagini, file: vedi più sotto).

I campi fuori da questa tabella vengono ignorati senza errore. n, parallel_tool_calls, service_tier e stream_options compresi: l’API restituisce sempre una sola risposta, e nello streaming il conteggio dei token arriva comunque.

La risposta

{
"id": "chatcmpl_9f2c41a7b8d3e05614c9a1b2",
"object": "chat.completion",
"created": 1790841600,
"model": "openai/gpt-5.4-mini",
"choices": [
{
"index": 0,
"message": {
"role": "assistant",
"content": "Un preventivo elenca le voci di lavoro, i prezzi unitari e la validità dell'offerta."
},
"finish_reason": "stop"
}
],
"usage": {
"prompt_tokens": 31,
"completion_tokens": 22,
"total_tokens": 53
}
}

Il testo è in choices[0].message.content. model è l’id del modello che ha risposto, usage il conteggio dei token su cui la chiamata viene addebitata.

Guarda sempre finish_reason. stop vuol dire che la risposta è finita; length che si è fermata al tetto di max_tokens, e quindi è tagliata.

Esempi

Con curl:

curl https://api.userbot.ai/api/eu/v1/chat/completions \
-H "Authorization: Bearer $USERBOT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "anthropic/claude-sonnet-5",
"messages": [
{"role": "system", "content": "Sei un assistente commerciale. Rispondi in italiano."},
{"role": "user", "content": "Riassumi in tre punti cosa deve contenere un preventivo."}
]
}'

Con l’SDK OpenAI per Python:

import os
from openai import OpenAI
client = OpenAI(
api_key=os.environ["USERBOT_API_KEY"],
base_url="https://api.userbot.ai/api/eu/v1",
)
completion = client.chat.completions.create(
model="anthropic/claude-sonnet-5",
messages=[
{"role": "system", "content": "Sei un assistente commerciale. Rispondi in italiano."},
{"role": "user", "content": "Riassumi in tre punti cosa deve contenere un preventivo."},
],
)
print(completion.choices[0].message.content)

Con l’SDK OpenAI per JavaScript e TypeScript:

import OpenAI from "openai";
const client = new OpenAI({
apiKey: process.env.USERBOT_API_KEY,
baseURL: "https://api.userbot.ai/api/eu/v1",
});
const completion = await client.chat.completions.create({
model: "anthropic/claude-sonnet-5",
messages: [
{ role: "system", content: "Sei un assistente commerciale. Rispondi in italiano." },
{ role: "user", content: "Riassumi in tre punti cosa deve contenere un preventivo." },
],
});
console.log(completion.choices[0].message.content);

Streaming

Con "stream": true la risposta arriva come flusso di eventi (Server-Sent Events): ogni evento è un oggetto chat.completion.chunk con un pezzo di testo in choices[0].delta.content. L’ultimo pezzo porta finish_reason e usage, poi il flusso si chiude con data: [DONE].

stream = client.chat.completions.create(
model="anthropic/claude-sonnet-5",
messages=[{"role": "user", "content": "Scrivi una mail di sollecito cortese."}],
stream=True,
)
for chunk in stream:
if chunk.choices and chunk.choices[0].delta.content:
print(chunk.choices[0].delta.content, end="")

Se il credito o il budget finiscono mentre la risposta è in corso, il flusso si interrompe con un evento di errore al posto del pezzo successivo. Il tuo client deve trattarlo come una risposta incompleta, non come una risposta finita.

Strumenti

In tools descrivi le funzioni del tuo sistema. Quando il modello decide che ne serve una, la risposta ha finish_reason uguale a tool_calls e, in message.tool_calls, il nome della funzione con gli argomenti in JSON.

Userbot non esegue niente: la funzione la chiami tu. Poi rimandi la conversazione aggiungendo la risposta del modello con i suoi tool_calls e un messaggio con ruolo tool, che porta il risultato e il tool_call_id corrispondente. Il modello userà quel risultato per rispondere.

{
"model": "anthropic/claude-sonnet-5",
"messages": [
{"role": "user", "content": "A che punto è l'ordine 4821?"},
{"role": "assistant", "content": null, "tool_calls": [
{"id": "call_1", "type": "function",
"function": {"name": "stato_ordine", "arguments": "{\"numero\":\"4821\"}"}}
]},
{"role": "tool", "tool_call_id": "call_1", "content": "{\"stato\":\"spedito\",\"corriere\":\"BRT\"}"}
],
"tools": [
{"type": "function", "function": {
"name": "stato_ordine",
"description": "Restituisce lo stato di un ordine",
"parameters": {"type": "object", "properties": {"numero": {"type": "string"}}, "required": ["numero"]}
}}
]
}

Immagini e file nei messaggi

Il content di un messaggio può essere una lista di parti, come in OpenAI:

  • {"type": "text", "text": "..."} per il testo;
  • {"type": "image_url", "image_url": {"url": "..."}} per un’immagine, con un indirizzo pubblico o un data URL in base64;
  • {"type": "file", "file": {"filename": "offerta.pdf", "file_data": "..."}} per un file, con il contenuto codificato in base64.

Non tutti i modelli leggono immagini e file: per questi messaggi scegline uno che lo fa. Il corpo della richiesta non può superare 30 MB, allegati compresi.

Risposte in JSON

Con "response_format": {"type": "json_object"} chiedi al modello un JSON valido; con json_schema gli passi anche lo schema da rispettare. Scrivilo comunque anche nelle istruzioni: non tutti i modelli rispettano il formato allo stesso modo, quindi valida sempre la risposta prima di usarla.

Tenere il filo di una conversazione

L’endpoint non ha memoria: ogni chiamata parte da zero. Per continuare un dialogo rimandi tutti i messaggi precedenti, compresa la risposta ricevuta, e in fondo la nuova domanda.

La lista cresce a ogni turno, e con lei il costo. Se la conversazione è lunga, taglia i turni vecchi o sostituiscili con un riassunto nel messaggio system.

Gli errori

Gli errori arrivano con il codice HTTP corrispondente e un corpo JSON. La forma del corpo non è uguale per tutti i casi: decidi in base al codice HTTP e leggi il campo code quando c’è.

{
"message": "Invalid API key.",
"code": "invalid_api_key",
"error": "Unauthorized",
"statusCode": 401
}
CodiceCosa è successoCosa fare
400Corpo non valido (campo mancante, valore fuori intervallo) oppure modello sconosciuto su Global (Unknown model: …)Correggi la richiesta. message dice quale campo
401Chiave assente, errata, revocata o scadutaVedi Autenticazione e chiavi
402Credito prepagato esaurito, utilizzo incluso finito o periodo di prova terminatoSi risolve ricaricando o cambiando piano, non riprovando → Costi, budget e analytics
403La chiave non ha il permesso, l’indirizzo non è ammesso, la chiamata arriva da un browserVedi Autenticazione e chiavi
429Troppe richieste o troppi token al minuto, oppure budget raggiuntoSe code manca è la frequenza: aspetta e riprova. Se code è member_budget_exceeded o api_budget_exceeded è il budget: riprovare non serve → Limiti e quote
451Modello non disponibile sulla regione EU. Qui il corpo è nel formato OpenAI, con error.code uguale a region_not_allowedScegli un modello presente su EU, o usa Global
500, 503Userbot o il fornitore del modello non hanno rispostoRiprova con attesa crescente. Se si ripete, scrivi al supporto