API-referentie
Authenticatie, endpoints, streaming, reasoning en fouten op het OpenAI-oppervlak, de native Anthropic-API's en de compatibiliteitsmatrix per endpoint.
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 wordt bewaard. Een key is een credential voor een workload. De workload beheert rate limits, budget, toegestane modellen en beleidsafwijkingen; verzoeken blijven ook onder het organisatiebeleid vallen.
Een verzoek waarvan het model niet op de niet-lege allow-list van de workload 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, plus de native Anthropic-paden die verderop in detail staan. Endpoints zonder een eigen handler worden letterlijk doorgestuurd naar de gerouteerde provider, streaming inbegrepen, zodat OpenAI-compatibele upstreams volledige getrouwheid behouden. Een browser die een pad opent dat de API niet bedient, zoals de kale API-host, wordt doorgestuurd naar deze docs; API-clients krijgen nog steeds een gewone 404.
| Endpoint | Doel |
|---|---|
| POST / | Chat completions, het primaire oppervlak: routing, gegevensbescherming, caching, streaming. |
| POST / | Legacy text completions. |
| POST / | Embeddings. |
| POST / | Moderatieclassificatie. |
| POST / | De OpenAI Responses API. |
| POST / | Native System One typed decisions, not OpenAI chat messages. |
| POST / | 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 / | Documentanonimisering · PII wordt vervangen door «MERGE_TAG»s, afbeeldingen worden vervaagd · gefactureerd per pagina/afbeelding. |
| POST / | 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 / | De modellen die je beleid en credentials daadwerkelijk kunnen bereiken, niets hypothetisch. |
| GET / | Metadata van één model. |
| POST / | Transcriptie, vertaling, spraak · doorgestuurd naar de gerouteerde provider. |
| POST / | Beeldgeneratie en -bewerkingen · doorgestuurd. mistral/image-generation is de uitzondering: Mistral biedt beeldgeneratie alleen via zijn agent-connector, dus de gateway vertaalt de OpenAI-request en antwoordt met b64_json, gefactureerd per beeld. |
| POST / | Videogeneratie · doorgestuurd. |
| / | Bestandsbewerkingen · doorgestuurd met de eigen key van je organisatie (BYOK of eigen provider), nooit met beheerde keys. |
| POST / | Native Anthropic Messages-ingang · richt elke Anthropic-SDK-tool op Sluis; elk verbonden model. |
| POST / | Lokale tokenschatting voor Anthropic-SDK-contextbeheer; er wordt niets verzonden. |
| GET / | Native Anthropic-modeldiscovery · list en get in de Anthropic-vorm, uit de in-process catalogus. Op /v1/models kiest de anthropic-version-header dezelfde vorm. |
| HEAD / | Warmloop-probe voor Anthropic-SDK-clients zoals Claude Code · 200 met een leeg body. |
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 }
}
Elke JSON-body hierboven mag daarnaast de gateway-extensie sluis.remove dragen, die termen noemt die vóór verzending uit de prompt gepseudonimiseerd moeten worden. Ze wordt verwijderd voordat het verzoek de gateway verlaat; zie de Gegevensbeschermingsreferentie.
# 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"], "remove": ["Project Nightingale"], "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" }
]
}
System One typed decisions
# The router behind sluis/auto: send state only, not questions. curl https://api.sluis.ai/v1/systemone \ -H "Authorization: Bearer $SLUIS_KEY" \ -H "Content-Type: application/json" \ -d '{"model":"sluis/typed/router", "state":{"request":"Compare these two supplier contracts clause by clause and list the risks.", "previous":"Here are both contracts.", "attachments":["contract_a.pdf (application/pdf)","contract_b.pdf (application/pdf)"]}}'
{
"model": "sluis/typed/router",
"answers": {
"reasoning": { "type": "noul", "noul": 0.9412,
"probabilities": { "false": 0.0588, "true": 0.9412 },
"confidence": 0.9412, "answer_confidence": 0.9412 },
"complexity": { "type": "score", "score": 2.31,
"legend": { "0": "trivial", "1": "simple", "2": "moderate", "3": "hard" },
"probabilities": { "0": 0.01, "1": 0.12, "2": 0.42, "3": 0.45 },
"confidence": 0.2612, "answer_confidence": 0.45 },
"task": { "type": "choice", "choice": "document",
"probabilities": { "code": 0.01, "writing": 0.03, "translation": 0.01, "extraction": 0.05,
"document": 0.86, "support": 0.02, "smalltalk": 0.01, "action": 0.01 },
"confidence": 0.6888, "answer_confidence": 0.86 }
},
"decision": { "route": "sluis/reasoning", "demand": "strong" },
"revision": ""
} | Local-pack contract | Behaviour |
|---|---|
| Model ids | sluis/typed answers your own questions on a local open model (GLiNER2.5-Decide). sluis/typed/router is the router Sluis uses for sluis/auto, with its own calibrated questions. |
| sluis/ | Send the text a request carries: state.request (required), and optional previous, system and attachments (a string or a list of "name (mime)" strings); a plain string is the request. It answers reasoning (noul, P(needs multi-step reasoning)), complexity (score, 0 trivial to 3 hard, with legend and probabilities per level) and task (choice over code, writing, translation, extraction, document, support, smalltalk, action). decision holds the route and demand sluis/auto would take from this text alone, or null. Files, images, JSON mode, tools, prompt size and your organisation's policy are not part of this input and can change a real sluis/auto route. answers.reasoning keeps the keys it always had; the rest is additional. |
| Plain sluis/ | Always answered on Sluis infrastructure, never by an outside provider. When the local model is unavailable the call returns 503. GET /v1/models lists it only when it can answer. |
| TypeSafe | Bring your own key only; no sluis/* alias reaches it. Name the model explicitly, for example typesafe/jev-latest. The normal gate applies: it is a US provider, so your residency policy must allow US. |
| Availability | Every answer comes from verified model weights, reported as revision. If the local model is unavailable or cannot be verified, the call fails closed with 503. |
| State | Required JSON object, list (array) or string, at most 128 KiB (131072 bytes) of serialized JSON before DLP. Missing or invalid state returns 422; oversized state returns 413. The open pack reads the first 384 words. |
| Questions | sluis/typed/router supplies its own calibrated questions: any questions field, including null or an empty object, returns 400. Plain sluis/typed requires 1 to 16 questions of type noul, choice (2 to 20 options) or score (2 to 20 levels), each with instructions; missing questions return 422, a malformed set 400, more than 64 KiB 413. The characters [ ] ( ) and backticks are removed before the model reads them, so options that then collide return 400, as does a set longer than about 1,000 tokens. |
| Response | JSON with model, answers and revision (weights SHA256); sluis/typed/router adds decision. Each answer has its type, its typed value (choice, score or noul), confidence and, where the pack reports them, probabilities, legend and answer_confidence. On sluis/typed gate on answer_confidence; its probabilities are uncalibrated. No chat completion or streaming envelope. |
| Latency | sluis/typed/router usually answers well under 200 ms; when busy it returns 503 with Retry-After: 2. Plain sluis/typed runs all questions in one CPU pass: about 2 s for a prompt-sized state, up to about 7 s for a long one, with a 10 s budget. |
| Data protection | Tenant DLP applies locally to the state and to the questions' instructions and criteria: block refuses; tokenize or mask rewrites what the pack is shown. Per-request removal terms are honoured. |
| Residency | Processed on Sluis infrastructure with no outside provider and no upstream residency routing step. |
| Metering | Each successful local decision is sealed and billed per input token at €0.01 per 1M tokens; output is free. The pack reports no token count, so input is estimated at about 4 characters per token over the state and questions it was shown. Normal limits, budgets and model allow-lists still apply. |
| Aliases | An existing tenant alias with the same name takes precedence over the local pack. |
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.
Na 15 seconden zonder frame schrijft de gateway tussen events een SSE-commentaarregel (: keepalive), zodat proxy's met een idle-timeout de verbinding openhouden terwijl een model nadenkt; SSE-clients negeren die. Streams dragen Cache-Control: no-cache en X-Accel-Buffering: no. Wanneer een gateway-replica herstart, bedient die open streams nog tot vijf minuten; een stream die daarna nog open is, eindigt met een error-event met code server_restarting, dus probeer het verzoek opnieuw.
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
)Prompt-caching
Terugkerende promptprefixen — een lange systeemprompt, tooldefinities, een groeiend gesprek — kunnen bij de provider gecacht worden: goedkoper en sneller. Sluis spreekt de standaardmarkers van de industrie: cache_control-blokken in Anthropic-stijl (met een optionele TTL van 5m of 1h) en OpenAI's top-level prompt_cache_key. Stuur ze in één vorm; Sluis vertaalt of verwijdert ze per provider, zodat hetzelfde verzoek over de hele catalogus werkt.
| Provider | Gedrag prompt-cache |
|---|---|
| Anthropic | Expliciete cache_control-breekpunten op systeemblokken, berichtinhoud en tooldefinities, met een TTL van 5m of 1h. Cache-reads en cache-writes worden apart gerapporteerd. |
| Bedrock | cache_control wordt vertaald naar Converse cachePoint-blokken in system, messages en toolConfig — hetzelfde verzoek, niets te herschrijven aan jouw kant. |
| OpenAI · Azure | Automatische caching op lange prefixen. Markers worden verwijderd; een optionele prompt_cache_key gaat ongewijzigd mee zodat vervolgaanroepen cache-affiniteit houden. |
| Gemini · Vertex | Bij Gemini-modellen cachet de provider impliciet — niets te sturen, en het gerapporteerde aandeel gecachte tokens gaat door naar je verbruik. Claude op Vertex neemt expliciete cache_control, TTL en al, precies zoals bij Anthropic. |
| xAI · DeepSeek · Mistral · Qwen · Moonshot · Zhipu | Automatisch waar de provider het aanbiedt. Expliciete markers worden weggefilterd, dus een verzoek dat voor Claude is geschreven wordt hier nooit geweigerd. |
| Scaleway · custom | Scaleway heeft geen prompt-cache bij de provider: directives worden daar weggefilterd en de hendel is de optionele responscache van de gateway. Een eigen endpoint krijgt beide directives ongewijzigd — dat gedrag is het contract van de operator. |
Expliciete prompt-caching is opt-in op organisatieniveau en staat standaard uit: een gecachte prefix is data at rest bij de provider — eerst door de DLP van Sluis gefilterd, maar buiten Sluis bewaard voor de cache-TTL. Zet het aan in de weergave Gegevensbescherming van de Console. Sommige providers (OpenAI, Gemini, xAI, DeepSeek) cachen toch al automatisch in hun eigen infrastructuur; de schakelaar bepaalt alleen wat Sluis expliciet doorstuurt (cache_control, cachePoint, prompt_cache_key), niet het interne gedrag van de provider. Zonder eigen sleutel voor een provider lopen aanroepen via een Sluis-credential die andere organisaties delen: bij OpenAI en Azure zet Sluis een uit je organisatie afgeleide prompt_cache_key in plaats van de jouwe, en bij alle andere providers tonen de gecachte-tokentellers in je verbruik 0. De afrekening past nog steeds de echte cachekorting toe.
Cache-reads worden afgerekend tegen het verlaagde providertarief; cache-writes in Anthropic-stijl leggen de write-opslag van de provider op het inputtarief. Beide zijn per aanroep zichtbaar in de audit-drawer en opgeteld in Gebruik & budget. Met een door Sluis beheerde key wordt een verzoek dat een toeslag kiest die Sluis niet kan meten — een cache_control-ttl anders dan 5m waar de provider die honoreert (Anthropic, Bedrock, Claude op Vertex), een service_tier anders dan auto, default, flex of standard_only, of de long-context-bèta van Anthropic (context-1m) — geweigerd met 400 voordat er iets wordt verstuurd; je eigen provider-key stuurt alle drie ongewijzigd door.
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 workload; stel ze in via Console → Workloads. |
| 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 document is te groot voor de route die het nam: een pagina die zelfs op de minimale scanresolutie boven het pixelplafond van de rendering blijft, of een bestand boven de bytelimiet van tekstextractie. Grootformaatpagina's (A0/A1-tekeningen) worden automatisch gedownscaled gerenderd; de eigen code laat een client het document verkleinen of splitsen en opnieuw aanbieden. |
| 429 rate_limit_error | Rate limit bereikt. Afgedwongen op de gateway; het verzoek bereikt nooit een provider. |
| 403 permission_error | Geblokkeerd door het residency-beleid: geen toegestane jurisdictie bedient het verzoek. De body bevat de reden. |
| 403 scope_violation | Geweigerd door de scopebewaking van de workload in modus block: de call valt buiten de scope die voor die workload is geschreven. Het bericht bevat de reden van de bewaking en het verzoek bereikt nooit een provider. |
| 503 pdf_renderer_unavailable | Een inline PDF moest als paginabeelden worden verstuurd omdat het gerouteerde model beeldinvoer leest, maar deze deployment heeft geen PDF-renderer. Het verzoek wordt geweigerd in plaats van stilzwijgend beantwoord op basis van geëxtraheerde tekst, wat alles zou weggooien wat een tekening of scan toont. |
| 503 scope_guard_unavailable | De scopebewaking van de workload staat op block maar kwam niet tot een oordeel, dus de call wordt geweigerd in plaats van doorgelaten. Probeer opnieuw met backoff. |
| 503 auth_unavailable | Bramka nie mogła odczytać magazynu kluczy, więc nie mogła zweryfikować klucza. Klucz nie jest uznany za nieprawidłowy; ponów z rosnącym odstępem i uwzględnij Retry-After. |
| 504 api_error | De provider antwoordde niet binnen de antwoordtermijn van de workload: 300 seconden tenzij de workload 10 tot 600 instelt, korter als het verzoek x-sluis-upstream-timeout meestuurde. error.sluis.cause is timeout. De gateway heeft de call niet opnieuw verstuurd omdat de provider er nog mee bezig kan zijn; kort de prompt in of vraag een langere termijn voordat je opnieuw verstuurt. |
| 5xx api_error | Upstream-providerfout na retries; de circuit breaker leidt verkeer om onhealthy providers heen. De body bevat het bericht van de provider zelf plus een error.sluis-object dat de oorzaak noemt en elke poging, met de upstream-status of transportfout en de duur. |
{
"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": "provider `openai` (jurisdiction `US`) is not permitted by the tenant's residency policy",
"type": "permission_error",
"param": null,
"code": "permission_denied"
}
}Anthropic Messages API
Sluis spreekt de Anthropic Messages API native. Richt Claude Code of een willekeurige Anthropic-SDK op de gateway en roep POST /v1/messages aan: het verzoek wordt naar de interne vorm vertaald, doorloopt precies dezelfde Inspect → Route → Seal → Meter-pijplijn als elke andere aanroep en wordt terugvertaald naar de Anthropic-vorm, streaming inbegrepen. /anthropic/v1/messages is het ondubbelzinnige pad met prefix naar dezelfde handler.
Authenticatie
Stuur je Sluis-key mee in de x-api-key-header van Anthropic of in de gebruikelijke Authorization: Bearer-header; beide worden geaccepteerd. De anthropic-version-header wordt gelezen maar nooit vereist, en alleen de vorm ervan wordt gevalideerd. Het parsen is bewust open: onbekende bodyvelden, onbekende contentbloktypen, onbekende anthropic-beta-waarden en de query ?beta=true die Claude Code meestuurt worden getolereerd in plaats van geweigerd. Beta-flags gaan byte voor byte mee naar upstreams uit de Anthropic-familie en worden elders genegeerd.
Endpoints
| Endpoint | Doel |
|---|---|
| POST / | Messages, gebufferd of streamend · systeemprompts als string of blokken, tools en tool-resultaten (ook foutresultaten en afbeeldingsblokken daarin), afbeeldingen, documentblokken, thinking- en redacted_thinking-blokken uit de historie, en cache_control-markers op elke cachebare positie. |
| POST / | Lokale tokenschatting voor contextbeheer in de SDK: er wordt niets verzonden, bewaard of gemeterd. |
| GET / | Native modeldiscovery in de Anthropic-vorm (data, has_more, first_id, last_id, display_name, created_at, best-effort before_id/after_id/limit-cursors), geantwoord uit de in-process catalogus zonder databasegang. Op het rootpad kiest de anthropic-version-header deze vorm — zonder die header krijg je de OpenAI-vorm; onder /anthropic geldt hij altijd. Id's blijven de door Sluis aanroepbare provider/model-id's en created_at is een vaste placeholder, want de catalogus kent geen releasedatum per model. |
| HEAD / | De warmloop-probe die Claude Code vóór een sessie stuurt · alleen HEAD, geantwoord met 200 en een leeg body. |
Modelroutering
Het wire-formaat beperkt het model nooit: de ingang is een codec en het model-id bepaalt de route. Een kaal id zoals claude-sonnet-5 lost op via de standaardprovider van deze ingang — anthropic tenzij je organisatie hem elders richt, bijvoorbeeld vertex voor Claude in de EU — waarna de residency-poort nog altijd beslist of dat doel het verzoek mag bedienen. Pin een doel expliciet met een provider-geprefixt id (vertex/claude-opus-4-8) of geef een sluis/*-alias mee: een Anthropic-SDK-tool loopt dan op elke aangesloten provider, binnen je beleid.
Streaming
Met "stream": true is het antwoord de volledige Anthropic-eventgrammatica: message_start met het echte aantal inputtokens, content_block_start / content_block_delta / content_block_stop per blok, message_delta met cumulatief gebruik inclusief cache-read- en cache-creation-tokens, en dan message_stop. Tekst komt als text_delta, tool-argumenten als input_json_delta en reasoning als thinking_delta plus signature_delta wanneer de gerouteerde provider die heeft geproduceerd. Een ping-event volgt op message_start en herhaalt zich na ongeveer 15 seconden stilte upstream, zodat een lang nadenkende beurt nooit een client-watchdog laat afgaan, en een fout halverwege de stream wordt heruitgegeven als een error-event in plaats van een afgekapte stream. stop_sequence draagt de gematchte reeks wanneer het antwoord van een provider uit de Anthropic-familie kwam en is anders null.
# native Anthropic Messages — the model id owns the route, not the wire format curl https://api.sluis.ai/v1/messages \ -H "x-api-key: $SLUIS_KEY" \ -H "anthropic-version: 2023-06-01" \ -H "Content-Type: application/json" \ -d '{ "model": "vertex/claude-opus-4-8", "max_tokens": 256, "system": "Be concise.", "messages": [{ "role": "user", "content": "Say hi" }] }' # /anthropic/v1/messages is the unambiguous prefixed path for the same handler
{
"id": "msg_01f7…",
"type": "message",
"role": "assistant",
"model": "claude-opus-4-8",
"content": [{ "type": "text", "text": "Hi! How can I help?" }],
"stop_reason": "end_turn",
"stop_sequence": null,
"usage": {
"input_tokens": 9,
"output_tokens": 8,
"cache_read_input_tokens": 0,
"cache_creation_input_tokens": 0
}
}# "stream": true — the full event grammar, ping keep-alives included
event: message_start
data: {"type":"message_start","message":{"id":"msg_01f7…","role":"assistant","model":"claude-opus-4-8","usage":{"input_tokens":9,"output_tokens":0}}}
event: ping
data: {"type":"ping"}
event: content_block_start
data: {"type":"content_block_start","index":0,"content_block":{"type":"text","text":""}}
event: content_block_delta
data: {"type":"content_block_delta","index":0,"delta":{"type":"text_delta","text":"Hi!"}}
event: ping
data: {"type":"ping"}
event: content_block_stop
data: {"type":"content_block_stop","index":0}
event: message_delta
data: {"type":"message_delta","delta":{"stop_reason":"end_turn","stop_sequence":null},"usage":{"output_tokens":8,"cache_read_input_tokens":0}}
event: message_stop
data: {"type":"message_stop"}Fouten
Elke fout op deze paden gebruikt de envelop van Anthropic — een "type": "error" op het hoogste niveau met het foutentype van de leverancier (invalid_request_error, authentication_error, billing_error, permission_error, not_found_error, conflict_error, request_too_large, rate_limit_error, api_error, overloaded_error) — inclusief de weigeringen die nooit een model bereiken, zoals een onbekende key of een blokkade door gegevensbescherming. Het request-id van de gateway staat als request-id-header op de respons en als request_id-veld in het body, en dat is hetzelfde id waarmee de aanroep in het auditspoor terugkomt.
# every failure on /v1/messages* is Anthropic-shaped — auth and # data-protection refusals included, never an OpenAI envelope { "type": "error", "error": { "type": "rate_limit_error", "message": "rate limit exceeded" }, "request_id": "req_9f2e…" }
# mcp_servers is refused, not quietly passed through: an external MCP # server called by the model would sit outside the Sluis gate { "type": "error", "error": { "type": "invalid_request_error", "message": "mcp_servers is not supported: register the server in Sluis and call the MCP gateway at /v1/mcp" }, "request_id": "req_4c81…" }
Eén requestveld wordt geweigerd in plaats van doorgestuurd: mcp_servers geeft 400 invalid_request_error. Een model dat zelf een externe MCP-server aanroept zou buiten de Sluis-poort vallen, dus registreer de server in de Console en gebruik de MCP-gateway op /v1/mcp, waar elke tool-aanroep wordt geïnspecteerd, gerouteerd, verzegeld en gemeterd.
Compatibiliteitsmatrix
Wat de native ingangen vandaag dekken, endpoint voor endpoint. Deze tabel ís de claim: wat hier niet als ondersteund staat, is roadmap, wordt bewust geweigerd of is een ander productoppervlak — nooit stilzwijgend gesuggereerd.
| Anthropic | Status | Toelichting |
|---|---|---|
| POST / | ✓ native | Gebufferd en streamend, tools, afbeeldingen, documentblokken, thinking-retour en prompt-cachemarkers. |
| POST / | ✓ lokale schatting | Lokaal geantwoord; er wordt niets verzonden. |
| GET / | ✓ native | Native list- en get-vorm uit de in-process catalogus; op het rootpad via header-onderhandeling, onder /anthropic altijd. |
| HEAD / | ✓ | De warmloop-probe van Claude Code, alleen HEAD. |
| Message Batches | ✗ roadmap | Message Batches zijn niet geïmplementeerd; batchwerk loopt vandaag als gewone live aanroepen. |
| Files | ✗ roadmap | De Files API is niet doorgeschakeld; inline documentblokken en file_data dekken het gangbare pad. |
| mcp_servers | ✗ geweigerd | Geweigerd met 400 in plaats van doorgestuurd — registreer de server en roep de Sluis MCP-gateway op /v1/mcp aan. |
| Skills · Agents · Admin | ✗ buiten scope | Skills, managed agents en de admin-, gebruiks- en kosten-API's zijn een ander productoppervlak, geen inferentiecompatibiliteit. |
Routering tussen protocollen. Leverancierseigen velden blijven bewaard zolang het verzoek landt bij een provider die hetzelfde protocol spreekt: een Anthropic-verzoek dat door een provider uit de Anthropic-familie wordt bediend houdt zijn thinking-blokken, cachemarkers, beta-flags en gematchte stop_sequence. Gaat hetzelfde verzoek naar een ander protocol, dan vallen die velden bij verzending weg — nooit een weigering bij de ingang — en de auditregel legt zowel het wire-formaat als de gekozen provider vast, zodat elk verlies toewijsbaar is. Ondoorzichtige waarden worden nooit herschreven: thinking-signatures en redacted_thinking-payloads gaan letterlijk mee of helemaal niet, en daarom wordt een geparkeerd thinking-blok dat de gegevensbeschermingsscan markeert weggelaten in plaats van gemaskeerd — een herschreven blok zou een ongeldige signature dragen.