Riferimento API
L'autenticazione, la superficie di endpoint compatibile OpenAI, lo streaming, il reasoning e la tassonomia degli errori.
Autenticazione
Ogni richiesta a /v1/* si autentica con una chiave virtuale nell'header Authorization: Bearer. Genera le chiavi nella Console; il segreto viene mostrato una sola volta e ne viene memorizzato solo l'hash. Una chiave porta i propri rate limit, budget, tag, una lista opzionale di modelli consentiti (vuota = qualsiasi modello) e option_overrides sparsi che deviano dalla policy dell'organizzazione (modalità di protezione dei dati, modello NER, modalità della scansione injection).
Una richiesta il cui modello non è nella lista di consentiti non vuota della chiave viene rifiutata con un 403 permission_error sigillato prima che venga inoltrato alcunché; il rifiuto stesso finisce nella catena di audit.
Generare una chiave di produzione richiede un indirizzo email verificato.
curl https://api.sluis.ai/v1/models \ -H "Authorization: Bearer $SLUIS_KEY" # a model outside the key's allow-list never dispatches: # → 403 permission_error · the refusal is sealed in the audit chain
{
"object": "list",
"data": [
{ "id": "mistral/mistral-large-latest", "object": "model", "owned_by": "mistral" },
{ "id": "vertex/claude-opus-4-8", "object": "model", "owned_by": "vertex" },
{ "id": "sluis/auto", "object": "model", "owned_by": "sluis" }
]
}Endpoint
Sluis espone la superficie compatibile OpenAI qui sotto. Gli endpoint senza un handler di prima classe vengono inoltrati alla lettera al provider instradato, streaming incluso, così gli upstream compatibili OpenAI mantengono piena fedeltà.
| Endpoint | Scopo |
|---|---|
| POST /v1/chat/completions | Chat completions, la superficie principale: instradamento, protezione dei dati, caching, streaming. |
| POST /v1/completions | Text completions legacy. |
| POST /v1/embeddings | Embeddings; alimenta anche la cache semantica. |
| POST /v1/moderations | Classificazione di moderazione. |
| POST /v1/responses | L'API Responses di OpenAI. |
| POST /v1/ocr | OCR di documenti (Mistral, o completamente locale con il modello sluis/ocr) · fatturato a pagina. Con la policy dlp_documents attiva, i documenti inline vengono anonimizzati prima di uscire. |
| POST /v1/documents/anonymize | Anonimizzazione di documenti · i PII diventano «MERGE_TAG», le immagini vengono sfocate · fatturato per pagina/immagine. |
| POST /v1/documents/anonymize/jobs | Job di anonimizzazione asincroni · accoda documenti grandi, controlla lo stato e scarica il risultato con un URL firmato a tempo senza chiave API. |
| GET /v1/models | I modelli che la tua policy e le tue credenziali possono davvero raggiungere, niente di ipotetico. |
| GET /v1/models/{id} | I metadati di un modello. |
| POST /v1/audio/* | Trascrizione, traduzione, voce · inoltrato al provider instradato. |
| POST /v1/images/* | Generazione e modifica di immagini · inoltrato. |
| POST /v1/video/generations | Generazione di video · inoltrato. |
| /v1/files | Operazioni sui file · inoltrato. |
| POST /v1/messages | Ingresso nativo Anthropic Messages · punta qualsiasi tool con SDK Anthropic verso Sluis; qualsiasi modello connesso. |
| POST /v1/messages/count_tokens | Stima locale dei token per la gestione del contesto dell'SDK Anthropic; non viene inviato nulla. |
| POST /v1beta/models/{model}:generateContent | Ingresso nativo Google Gemini · punta un SDK Gemini verso Sluis (:streamGenerateContent trasmette in streaming). |
curl https://api.sluis.ai/v1/chat/completions \ -H "Authorization: Bearer $SLUIS_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "mistral/mistral-large-latest", "messages": [{ "role": "user", "content": "Say hi" }] }'
{
"id": "chatcmpl-9f2e…",
"object": "chat.completion",
"model": "mistral-large-latest",
"choices": [{
"index": 0,
"message": { "role": "assistant", "content": "Hi! How can I help?" },
"finish_reason": "stop"
}],
"usage": { "prompt_tokens": 9, "completion_tokens": 8, "total_tokens": 17 }
}# Document OCR, billed per page. Returns pages[] markdown + usage_info.pages_processed. # model "sluis/ocr" runs the gateway's own OCR engine: fully local, no provider egress (inline data: URLs only). curl https://api.sluis.ai/v1/ocr \ -H "Authorization: Bearer $SLUIS_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "mistral/mistral-ocr-latest", "document": { "type": "document_url", "document_url": "https://example.com/invoice.pdf" } }'
{
"pages": [{
"index": 0,
"markdown": "# Invoice 2026-118\nAcme BV · Keizersgracht 1…",
"images": [],
"dimensions": { "dpi": 150, "height": 1754, "width": 1240 }
}],
"model": "mistral-ocr-latest",
"usage_info": { "pages_processed": 1, "doc_size_bytes": 48213 }
}# Document anonymization, billed per page/image. PII becomes «MERGE_TAG»s; images are blurred. curl https://api.sluis.ai/v1/documents/anonymize \ -H "Authorization: Bearer $SLUIS_KEY" \ -F file=@contract.docx \ -F 'options={ "entities": ["person_name", "email", "iban"], "include_mapping": true }'
{
"filename": "contract.docx",
"content_type": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"content_base64": "UEsDBBQABgAIA…",
"summary": { "pages": 4, "images": 1, "categories": ["EMAIL", "IBAN", "PERSON_NAME"], "downgraded": false },
"mapping": [
{ "token": "«PERSON_NAME_1»", "original": "Jan de Vries" },
{ "token": "«IBAN_1»", "original": "NL91ABNA0417164300" }
]
}Documenti in input
Allega un PDF, un documento Word, un'immagine o un file di testo a una richiesta chat come parte di contenuto "type": "file" con una URL file_data in base64. Qualsiasi modello instradato la accetta: i provider che capiscono nativamente le parti file le ricevono invariate, e per tutti gli altri il gateway converte il documento prima dell'invio. I riferimenti file_id lato provider non vengono risolti; includi i byte come file_data.
I modelli con visione ricevono ogni pagina del PDF renderizzata come parte image_url (budget di 48 pagine per richiesta, condiviso tra tutti i documenti allegati); i modelli senza input immagine ricevono il testo estraibile del documento inserito come testo del prompt, che la scansione di protezione dei dati copre poi come ogni altro testo. I documenti più lunghi o pesanti vanno su /v1/ocr. Con la policy dlp_documents attiva, il documento viene anonimizzato o rifiutato prima che qualcosa lasci il gateway. Se è disattivata e la modalità DLP dell'organizzazione è protettiva (tokenize, mask o block), i documenti che viaggerebbero come immagini non ispezionabili vengono rifiutati; con allow_log passano con un marcatore esplicito di non analizzato nel registro di audit.
# Attach a document as an OpenAI-style `file` content part. The gateway # normalizes it for the routed provider, so this works on any model. curl https://api.sluis.ai/v1/chat/completions \ -H "Authorization: Bearer $SLUIS_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "scaleway/gemma-4-26b-a4b-it", "messages": [{ "role": "user", "content": [ { "type": "file", "file": { "filename": "drawing.pdf", "file_data": "data:application/pdf;base64,'"$(base64 -w0 drawing.pdf)"'" } }, { "type": "text", "text": "Which discipline does this drawing document?" } ] }] }'
import OpenAI from "openai"; import { readFileSync } from "node:fs"; const client = new OpenAI({ baseURL: "https://api.sluis.ai/v1", apiKey: process.env.SLUIS_KEY, }); const pdf = readFileSync("drawing.pdf").toString("base64"); const reply = await client.chat.completions.create({ model: "vertex/gemini-3.1-pro", messages: [{ role: "user", content: [ { type: "file", file: { filename: "drawing.pdf", file_data: `data:application/pdf;base64,${pdf}` } }, { type: "text", text: "Which discipline does this drawing document?" }, ], }], });
Streaming
Imposta stream: true e la risposta arriva come server-sent events: ogni frame è un delta chat.completion.chunk e lo stream termina con data: [DONE]. Gli stream non vengono mai bufferizzati nel gateway: lo attraversano in tee, e il sigillo di audit e la misurazione avvengono anche se il client si disconnette in anticipo.
Richiedi stream_options.include_usage e il frame finale porta l'uso esatto dei token, gli stessi numeri che il gateway misura e fattura.
stream = client.chat.completions.create(
model="mistral/mistral-large-latest",
messages=[{"role": "user", "content": "Write a haiku"}],
stream=True,
stream_options={"include_usage": True},
)
for chunk in stream:
print(chunk.choices[0].delta.content or "", end="")const stream = await client.chat.completions.create({ model: "mistral/mistral-large-latest", messages: [{ role: "user", content: "Write a haiku" }], stream: true, stream_options: { include_usage: true }, }); for await (const chunk of stream) { process.stdout.write(chunk.choices[0]?.delta?.content ?? ""); }
# the raw event stream on the wire
data: {"object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"Water"}}]}
data: {"object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":" finds a way."}}]}
data: {"object":"chat.completion.chunk","choices":[{"index":0,"delta":{},"finish_reason":"stop"}],"usage":{"prompt_tokens":12,"completion_tokens":9,"total_tokens":21}}
data: [DONE]Ragionamento
Passa il parametro OpenAI reasoning_effort (minimal | low | medium | high) su qualsiasi modello capace di ragionamento. Sluis lo traduce per provider (il thinking level di Gemini, l'adaptive thinking effort di Claude) e lo omette dove un modello lo rifiuterebbe, così un solo parametro funziona su tutto il catalogo.
resp = client.chat.completions.create(
model="vertex/claude-opus-4-8",
messages=[{"role": "user", "content": "Prove it step by step…"}],
reasoning_effort="high", # minimal | low | medium | high
)Codici di errore
Gli errori usano l'envelope di errore di OpenAI; il valore error.type rispecchia lo stato HTTP, così la gestione degli errori del tuo SDK continua a funzionare invariata.
| Codice | Quando |
|---|---|
| 400 invalid_request_error | Corpo della richiesta o parametri malformati. |
| 400 invalid_request_error | Id del modello senza prefisso del provider. Ogni id richiamabile è provider/model, ad es. mistral/mistral-large-latest; il body riporta: model must be provider-prefixed. |
| 400 invalid_request_error | La richiesta porta l'header x-sluis-dlp, rimosso. Le deroghe per richiesta sono state sostituite dagli option overrides per chiave; configurali in Console → API keys. |
| 401 authentication_error | Chiave API mancante o sconosciuta. |
| 402 insufficient_quota | Nessun piano attivo o budget raggiunto. Attiva un piano o alza il budget; la richiesta non raggiunge mai un provider. |
| 403 permission_error | La chiave non ha il permesso, ad esempio il modello non è nella sua lista di consentiti. |
| 422 invalid_request_error | Rifiutata prima dell'inoltro, ad esempio la protezione dei dati in modalità block ha trovato corrispondenza nella richiesta. |
| 422 document_too_large_to_scan | Una pagina del documento supera il tetto di pixel di rendering anche alla risoluzione minima di scansione. Le pagine di grande formato (disegni A0/A1) vengono renderizzate ridotte automaticamente; il codice dedicato consente al client di ridurre la pagina e reinviarla. |
| 429 rate_limit_error | Rate limit raggiunto. Applicato al gateway; la richiesta non raggiunge mai un provider. |
| 451 permission_error | Bloccata dalla policy di residenza: nessuna giurisdizione consentita serve la richiesta. Il body include il motivo. |
| 5xx api_error | Errore del provider upstream dopo i retry; il circuit breaker devia il traffico attorno ai provider non integri. |
{
"error": {
"message": "model must be provider-prefixed, e.g. mistral/mistral-large-latest",
"type": "invalid_request_error",
"param": null,
"code": "invalid_request"
}
}{
"error": {
"message": "model is not on this key's allow-list",
"type": "permission_error",
"param": null,
"code": "permission_denied"
}
}{
"error": {
"message": "blocked by residency policy: provider jurisdiction US is not in the allowed set [EU]",
"type": "permission_error",
"param": null,
"code": "permission_denied"
}
}