> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.userbot.ai/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.userbot.ai/_mcp/server.

# Completamenti chat

`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](/api-auth)

## La richiesta

| Campo                                   | Obbligatorio | Cosa fa                                                                           |
| --------------------------------------- | ------------ | --------------------------------------------------------------------------------- |
| `model`                                 | Sì           | L'id del modello, nella forma `fornitore/modello`. Senza, ricevi `400`            |
| `messages`                              | Sì           | La conversazione, in ordine. Almeno un elemento                                   |
| `stream`                                | No           | `true` per ricevere la risposta a pezzi, man mano che viene scritta               |
| `temperature`                           | No           | Da 0 a 2                                                                          |
| `top_p`                                 | No           | Da 0 a 1                                                                          |
| `max_tokens` o `max_completion_tokens`  | No           | Tetto alla lunghezza della risposta. Senza, il tetto è 16.384 token               |
| `stop`                                  | No           | Una stringa, o da 1 a 4 stringhe, che fermano la generazione                      |
| `presence_penalty`, `frequency_penalty` | No           | Da -2 a 2                                                                         |
| `seed`                                  | No           | Un intero, per risposte più ripetibili dove il modello lo supporta                |
| `tools`, `tool_choice`                  | No           | Le funzioni che il modello può chiederti di eseguire, al massimo 128              |
| `response_format`                       | No           | `text`, `json_object` o `json_schema`                                             |
| `reasoning_effort`                      | No           | `none`, `minimal`, `low`, `medium`, `high` o `xhigh`, per i modelli che ragionano |
| `user`                                  | No           | Un 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

```json
{
  "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`:

```bash
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:

```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:

```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]`.

```python
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.

```json
{
  "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'è.

```json
{
  "message": "Invalid API key.",
  "code": "invalid_api_key",
  "error": "Unauthorized",
  "statusCode": 401
}
```

| Codice       | Cosa è successo                                                                                                                 | Cosa fare                                                                                                                                                                        |
| ------------ | ------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400`        | Corpo non valido (campo mancante, valore fuori intervallo) oppure modello sconosciuto su **Global** (`Unknown model: …`)        | Correggi la richiesta. `message` dice quale campo                                                                                                                                |
| `401`        | Chiave assente, errata, revocata o scaduta                                                                                      | Vedi [Autenticazione e chiavi](/api-auth)                                                                                                                                        |
| `402`        | Credito prepagato esaurito, utilizzo incluso finito o periodo di prova terminato                                                | Si risolve ricaricando o cambiando piano, non riprovando → [Costi, budget e analytics](/api-costi)                                                                               |
| `403`        | La chiave non ha il permesso, l'indirizzo non è ammesso, la chiamata arriva da un browser                                       | Vedi [Autenticazione e chiavi](/api-auth)                                                                                                                                        |
| `429`        | Troppe richieste o troppi token al minuto, oppure budget raggiunto                                                              | Se `code` manca è la frequenza: aspetta e riprova. Se `code` è `member_budget_exceeded` o `api_budget_exceeded` è il budget: riprovare non serve → [Limiti e quote](/api-limiti) |
| `451`        | Modello non disponibile sulla regione **EU**. Qui il corpo è nel formato OpenAI, con `error.code` uguale a `region_not_allowed` | Scegli un modello presente su EU, o usa **Global**                                                                                                                               |
| `500`, `503` | Userbot o il fornitore del modello non hanno risposto                                                                           | Riprova con attesa crescente. Se si ripete, scrivi al [supporto](/supporto)                                                                                                      |