API-referentie
Authenticatie, het OpenAI-compatibele endpoint-oppervlak, streaming, reasoning en de fouttaxonomie.
Authenticatie
Elk verzoek naar /v1/* authenticeert met een virtuele key in de Authorization: Bearer-header. Keys maak je aan in de Console; het secret wordt één keer getoond en alleen de hash ervan wordt bewaard. Een key draagt zijn eigen rate limits, budget, tags, een optionele allow-list voor modellen (leeg = elk model) en spaarzame option_overrides die afwijken van het organisatiebeleid (gegevensbeschermingsmodus, NER-model, injectiescanmodus).
Een verzoek waarvan het model niet op de niet-lege allow-list van de key staat, wordt geweigerd met een verzegelde 403 permission_error voordat er iets wordt verzonden; de weigering zelf belandt in de auditketen.
Het aanmaken van een productie-key vereist een geverifieerd e-mailadres.
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" }
]
}Endpoints
Sluis biedt het onderstaande OpenAI-compatibele oppervlak. Endpoints zonder een eigen handler worden letterlijk doorgestuurd naar de gerouteerde provider, streaming inbegrepen, zodat OpenAI-compatibele upstreams volledige getrouwheid behouden.
| Endpoint | Doel |
|---|---|
| POST /v1/chat/completions | Chat completions, het primaire oppervlak: routing, gegevensbescherming, caching, streaming. |
| POST /v1/completions | Legacy text completions. |
| POST /v1/embeddings | Embeddings; voeden ook de semantische cache. |
| POST /v1/moderations | Moderatieclassificatie. |
| POST /v1/responses | De OpenAI Responses API. |
| POST /v1/ocr | Document-OCR (Mistral, of volledig lokaal met model sluis/ocr) · gefactureerd per pagina. Met het dlp_documents-beleid aan worden inline documenten geanonimiseerd voor vertrek. |
| POST /v1/documents/anonymize | Documentanonimisering · PII wordt vervangen door «MERGE_TAG»s, afbeeldingen worden vervaagd · gefactureerd per pagina/afbeelding. |
| POST /v1/documents/anonymize/jobs | Asynchrone anonimiseringsjobs · zet grote documenten in de wachtrij, volg de status en haal het resultaat op via een tijdgebonden ondertekende URL zonder API-sleutel. |
| GET /v1/models | De modellen die je beleid en credentials daadwerkelijk kunnen bereiken, niets hypothetisch. |
| GET /v1/models/{id} | Metadata van één model. |
| POST /v1/audio/* | Transcriptie, vertaling, spraak · doorgestuurd naar de gerouteerde provider. |
| POST /v1/images/* | Beeldgeneratie en -bewerkingen · doorgestuurd. |
| POST /v1/video/generations | Videogeneratie · doorgestuurd. |
| /v1/files | Bestandsbewerkingen · doorgestuurd. |
| POST /v1/messages | Native Anthropic Messages-ingang · richt elke Anthropic-SDK-tool op Sluis; elk verbonden model. |
| POST /v1/messages/count_tokens | Lokale tokenschatting voor Anthropic-SDK-contextbeheer; er wordt niets verzonden. |
| POST /v1beta/models/{model}:generateContent | Native Google Gemini-ingang · richt een Gemini-SDK op Sluis (:streamGenerateContent streamt). |
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" }
]
}Documenten als invoer
Voeg een PDF, Word-document, afbeelding of tekstbestand aan een chat-request toe als content-part "type": "file" met een base64 file_data data-URL. Elk gerouteerd model accepteert dit: providers die file-parts native begrijpen krijgen ze ongewijzigd, en voor alle andere converteert de gateway het document vóór verzending. File_id-referenties aan providerzijde worden niet opgelost; neem de bytes op als file_data.
Modellen met vision krijgen elke PDF-pagina gerenderd als image_url-part (budget van 48 pagina's per request, gedeeld over alle bijgevoegde documenten); modellen zonder beeldinvoer krijgen de extraheerbare tekst van het document als prompttekst, die de databeschermingsscan daarna dekt zoals elke andere tekst. Langere of zwaardere documenten horen op /v1/ocr. Met de dlp_documents-policy aan wordt het document geanonimiseerd of geweigerd voordat er iets de gateway verlaat. Staat die uit en is de DLP-modus van de organisatie beschermend (tokenize, mask of block), dan worden documenten die als niet-inspecteerbare afbeeldingen zouden reizen geweigerd; onder allow_log passeren ze met een expliciete niet-gescand-markering in het auditlog.
# 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
Zet stream: true en het antwoord komt binnen als server-sent events: elk frame is een chat.completion.chunk-delta en de stream eindigt met data: [DONE]. Streams worden nooit gebufferd in de gateway: ze lopen er via een tee doorheen, en de auditverzegeling en metering gebeuren zelfs als de client vroegtijdig ophangt.
Vraag om stream_options.include_usage en het laatste frame draagt het exacte tokengebruik, dezelfde cijfers die de gateway meet en factureert.
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]Redeneren
Geef de OpenAI-parameter reasoning_effort (minimal | low | medium | high) door op elk model dat kan denken. Sluis vertaalt hem per provider (het denkniveau van Gemini, de adaptieve denk-inspanning van Claude) en laat hem weg waar een model hem zou weigeren, zodat één parameter over de hele catalogus werkt.
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
)Foutcodes
Fouten gebruiken de OpenAI-foutenvelop; de waarde error.type weerspiegelt de HTTP-status, zodat de foutafhandeling van je SDK ongewijzigd blijft werken.
| Code | Wanneer |
|---|---|
| 400 invalid_request_error | Onjuiste request-body of parameters. |
| 400 invalid_request_error | Model-id zonder providerprefix. Elk aanroepbaar id is provider/model, bijv. mistral/mistral-large-latest; de body meldt: model must be provider-prefixed. |
| 400 invalid_request_error | Het verzoek draagt de verwijderde x-sluis-dlp header. Overrides per verzoek zijn vervangen door option overrides per key; stel ze in via Console → API keys. |
| 401 authentication_error | Ontbrekende of onbekende API-key. |
| 402 insufficient_quota | Geen actief plan of budget bereikt. Activeer een plan of verhoog het budget; het verzoek bereikt nooit een provider. |
| 403 permission_error | De key mist toestemming, bijvoorbeeld het model staat niet op de allow-list. |
| 422 invalid_request_error | Geweigerd vóór verzending, bijvoorbeeld gegevensbescherming in block-modus matchte het verzoek. |
| 422 document_too_large_to_scan | Een documentpagina blijft zelfs op de minimale scanresolutie boven het pixelplafond van de rendering. Grootformaatpagina's (A0/A1-tekeningen) worden automatisch gedownscaled gerenderd; de eigen code laat een client de pagina verkleinen en opnieuw aanbieden. |
| 429 rate_limit_error | Rate limit bereikt. Afgedwongen op de gateway; het verzoek bereikt nooit een provider. |
| 451 permission_error | Geblokkeerd door het residency-beleid: geen toegestane jurisdictie bedient het verzoek. De body bevat de reden. |
| 5xx api_error | Upstream-providerfout na retries; de circuit breaker leidt verkeer om onhealthy providers heen. |
{
"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"
}
}