Referencia de la API
La autenticación, los endpoints, el streaming, el razonamiento y los errores en la superficie de OpenAI, las API nativas de Anthropic y la matriz de compatibilidad por endpoint.
Autenticación
Cada petición a /v1/* se autentica con una clave virtual en la cabecera Authorization: Bearer. Acuñe claves en la Consola; el secreto se muestra una vez y solo se almacena su hash. Una clave autentica un workload. El workload define los límites de tasa, el presupuesto, los modelos permitidos y las excepciones; las peticiones siguen sujetas a la política de la organización.
Una petición cuyo modelo no está en la lista de permitidos no vacía del workload se rechaza con un 403 permission_error sellado antes de despachar nada; el propio rechazo queda registrado en la cadena de auditoría.
Acuñar una clave de producción requiere una dirección de correo verificada.

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 expone la superficie compatible con OpenAI que se muestra a continuación, además de las rutas nativas de Anthropic detalladas más abajo. Los endpoints sin un handler de primera clase se retransmiten literalmente al proveedor enrutado, streaming incluido, de modo que los upstreams compatibles con OpenAI conservan plena fidelidad. Un navegador que abre una ruta que la API no sirve, como el host de la API sin ruta, se redirige a esta documentación; los clientes de la API siguen recibiendo un simple 404.
| Endpoint | Propósito |
|---|---|
| POST / | Chat completions, la superficie principal: enrutamiento, protección de datos, caché, streaming. |
| POST / | Text completions heredadas. |
| POST / | Embeddings. |
| POST / | Clasificación de moderación. |
| POST / | La API de Responses de OpenAI. |
| POST / | Native System One typed decisions, not OpenAI chat messages. |
| POST / | OCR de documentos (Mistral, o totalmente local con el modelo sluis/ocr) · facturado por página. Con la política dlp_documents activa, los documentos inline se anonimizan antes de salir. |
| POST / | Anonimización de documentos · los PII se convierten en «MERGE_TAG»s, las imágenes se difuminan · facturado por página/imagen. |
| POST / | Trabajos de anonimización asíncronos · encole documentos grandes, consulte el estado y recoja el resultado con una URL firmada de duración limitada sin clave de API. |
| GET / | Los modelos que su política y sus credenciales pueden alcanzar de verdad, nada hipotético. |
| GET / | Metadatos de un modelo. |
| POST / | Transcripción, traducción, voz · retransmitido al proveedor enrutado. |
| POST / | Generación y edición de imágenes · retransmitido. mistral/image-generation es la excepción: Mistral solo ofrece generación de imágenes a través de su conector de agente, así que la gateway traduce la petición OpenAI y responde con b64_json, facturado por imagen. |
| POST / | Generación de vídeo · retransmitido. |
| / | Operaciones con archivos · retransmitido con la clave propia de su organización (BYOK o proveedor personalizado), nunca con claves gestionadas. |
| POST / | Entrada nativa Anthropic Messages · apunte cualquier herramienta con SDK de Anthropic a Sluis; cualquier modelo conectado. |
| POST / | Estimación local de tokens para la gestión de contexto del SDK de Anthropic; no se envía nada. |
| GET / | Descubrimiento nativo de modelos de Anthropic · list y get en la forma de Anthropic, desde el catálogo en proceso. En /v1/models la cabecera anthropic-version selecciona esa misma forma. |
| HEAD / | Sonda de calentamiento para clientes del SDK de Anthropic como Claude Code · 200 con cuerpo vacío. |
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 }
}
Cada cuerpo JSON de arriba puede llevar además la extensión de la gateway sluis.remove, que nombra los términos que deben pseudonimizarse en el prompt antes del envío. Se retira antes de que la petición salga de la gateway; véase la referencia de Protección de datos.
# 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. |
Documentos como entrada
Adjunte un PDF, un documento Word, una imagen o un archivo de texto a una petición de chat como parte de contenido "type": "file" con una URL file_data en base64. Cualquier modelo enrutado lo acepta: los proveedores que entienden partes de archivo de forma nativa las reciben sin cambios, y para todos los demás la pasarela convierte el documento antes del envío. Las referencias file_id del proveedor no se resuelven; incluya los bytes como file_data.
Los modelos con visión reciben cada página del PDF renderizada como parte image_url (presupuesto de 48 páginas por petición, compartido entre todos los documentos adjuntos); los modelos sin entrada de imagen reciben el texto extraíble del documento insertado como texto del prompt, que el escaneo de protección de datos cubre después como cualquier otro texto. Los documentos más largos o pesados corresponden a /v1/ocr. Con la política dlp_documents activada, el documento se anonimiza o se rechaza antes de que nada salga de la pasarela. Si está desactivada y el modo DLP de la organización es protector (tokenize, mask o block), los documentos que viajarían como imágenes no inspeccionables se rechazan; con allow_log pasan con un marcador explícito de no analizado en el registro de auditoría.
# 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
Ponga stream: true y la respuesta llega como server-sent events: cada frame es un delta chat.completion.chunk y el stream termina con data: [DONE]. Los streams nunca se almacenan en búfer en la pasarela: pasan a través de ella en modo tee, y el sellado de auditoría y la medición ocurren aunque el cliente cuelgue antes de tiempo.
Pida stream_options.include_usage y el frame final lleva el uso exacto de tokens, las mismas cifras que la pasarela mide y factura.
Tras 15 segundos sin frames, la pasarela escribe una línea de comentario SSE (: keepalive) entre eventos, para que los proxies con tiempo de inactividad mantengan la conexión abierta mientras un modelo piensa; los clientes SSE la ignoran. Los flujos llevan Cache-Control: no-cache y X-Accel-Buffering: no. Cuando una réplica de la pasarela se reinicia, sigue sirviendo los flujos abiertos hasta cinco minutos; un flujo aún abierto después termina con un evento de error con código server_restarting, así que reintente la solicitud.
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]Razonamiento
Pase el parámetro OpenAI reasoning_effort (minimal | low | medium | high) en cualquier modelo con capacidad de razonamiento. Sluis lo traduce por proveedor (el nivel de pensamiento de Gemini, el esfuerzo de pensamiento adaptativo de Claude) y lo omite donde un modelo lo rechazaría, así que un solo parámetro funciona en todo el catálogo.
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
)Caché de prompts
Los prefijos de prompt repetidos —un system prompt largo, definiciones de herramientas, una conversación que crece— pueden almacenarse en la caché del proveedor: cuestan menos y responden más rápido. Sluis habla los marcadores estándar del sector: bloques cache_control al estilo Anthropic (con un TTL opcional de 5m o 1h) y el parámetro de primer nivel prompt_cache_key de OpenAI. Envíelos en una sola forma; Sluis los traduce o los elimina según el proveedor, así que la misma petición funciona en todo el catálogo.
| Proveedor | Comportamiento de la caché de prompts |
|---|---|
| Anthropic | Puntos de corte cache_control explícitos en bloques de sistema, contenido de mensajes y definiciones de herramientas, con TTL de 5m o 1h. Las lecturas y escrituras de caché se reportan por separado. |
| Bedrock | cache_control se traduce a bloques cachePoint de Converse en system, messages y toolConfig: la misma petición, sin reescribir nada por su parte. |
| OpenAI · Azure | Caché automática en prefijos largos. Los marcadores se eliminan; un prompt_cache_key opcional se reenvía tal cual para conservar la afinidad de caché. |
| Gemini · Vertex | Con los modelos Gemini la caché es implícita dentro del proveedor: nada que enviar, y la proporción de tokens en caché reportada se traslada a su consumo. Claude en Vertex acepta cache_control explícito, TTL incluido, igual que en Anthropic. |
| xAI · DeepSeek · Mistral · Qwen · Moonshot · Zhipu | Automática donde el proveedor la ofrece. Los marcadores explícitos se sanean, así que una petición escrita para Claude nunca se rechaza aquí. |
| Scaleway · custom | Scaleway no tiene caché de prompts del proveedor: allí las directivas se sanean y la palanca es la caché de respuestas de la pasarela. Un endpoint propio recibe ambas directivas sin modificar: ese comportamiento es contrato del operador. |
La caché de prompts explícita se activa a nivel de organización y está desactivada por defecto: un prefijo en caché son datos en reposo en el proveedor —filtrados antes por el DLP de Sluis, pero almacenados fuera de Sluis durante el TTL de la caché—. Actívela en la vista Protección de datos de la Consola. Algunos proveedores (OpenAI, Gemini, xAI, DeepSeek) almacenan en caché automáticamente dentro de su propia infraestructura de todos modos; el interruptor solo rige lo que Sluis reenvía explícitamente (cache_control, cachePoint, prompt_cache_key), no el comportamiento interno del proveedor. Sin clave propia para un proveedor, las llamadas usan una credencial de Sluis compartida con otras organizaciones: en OpenAI y Azure, Sluis sustituye su prompt_cache_key por uno derivado de su organización, y en los demás proveedores los contadores de tokens en caché de su consumo muestran 0. La facturación sigue aplicando el descuento de caché real.
Las lecturas de caché se facturan a la tarifa reducida del proveedor; las escrituras de caché al estilo Anthropic añaden el recargo de escritura del proveedor sobre la tarifa de entrada. Ambas son visibles por llamada en el panel de auditoría y se suman en Uso y presupuesto. Con una clave gestionada por Sluis, una petición que selecciona un recargo que Sluis no puede medir —un ttl de cache_control distinto de 5m donde el proveedor lo aplica (Anthropic, Bedrock, Claude en Vertex), un service_tier distinto de auto, default, flex o standard_only, o la beta de contexto largo de Anthropic (context-1m)— se rechaza con 400 antes de enviar nada; su propia clave de proveedor reenvía los tres sin cambios.
Códigos de error
Los errores usan el sobre de error de OpenAI; el valor error.type refleja el estado HTTP, así que el manejo de errores de su SDK sigue funcionando sin cambios.
| Código | Cuándo |
|---|---|
| 400 invalid_request_error | Cuerpo de la petición o parámetros mal formados. |
| 400 invalid_request_error | Identificador de modelo sin prefijo de proveedor. Cada identificador invocable es provider/model, p. ej. mistral/mistral-large-latest; el cuerpo indica: model must be provider-prefixed. |
| 400 invalid_request_error | La petición lleva la cabecera x-sluis-dlp, ya eliminada. Las anulaciones por petición fueron sustituidas por los option overrides por workload; configúrelos en Consola → Workloads. |
| 401 authentication_error | Clave de API ausente o desconocida. |
| 402 insufficient_quota | Sin plan activo o presupuesto alcanzado. Active un plan o suba el presupuesto; la petición nunca llega a un proveedor. |
| 403 permission_error | La clave carece de permiso, por ejemplo el modelo no está en su lista de permitidos. |
| 422 invalid_request_error | Rechazada antes de despachar, por ejemplo la protección de datos en modo block coincidió con la petición. |
| 422 document_too_large_to_scan | Un documento es demasiado grande para la ruta que tomó: una página que sigue por encima del tope de píxeles de renderizado a la resolución mínima de escaneo, o un archivo por encima del tope de bytes de la extracción de texto. Las páginas de gran formato (planos A0/A1) se renderizan reducidas automáticamente; el código dedicado permite al cliente reducir o dividir el documento y reenviarlo. |
| 429 rate_limit_error | Límite de tasa alcanzado. Se aplica en la pasarela; la petición nunca llega a un proveedor. |
| 403 permission_error | Bloqueada por la política de residencia: ninguna jurisdicción permitida sirve la petición. El cuerpo incluye el motivo. |
| 403 scope_violation | Rechazada por el control de alcance del workload en modo block: la llamada queda fuera del alcance definido para ese workload. El mensaje incluye el motivo del control y la petición nunca llega a un proveedor. |
| 503 pdf_renderer_unavailable | Un PDF en línea debía enviarse como imágenes de página porque el modelo enrutado admite entrada de imagen, pero este despliegue no tiene renderizador de PDF. Se rechaza en lugar de responderse en silencio a partir del texto extraído, lo que descartaría todo lo que muestra un plano o un escaneo. |
| 503 scope_guard_unavailable | El control de alcance del workload está en modo block pero no pudo emitir un veredicto, así que la llamada se rechaza en lugar de dejarla pasar. Reintente con espera progresiva. |
| 503 auth_unavailable | Das Gateway konnte seinen Schlüsselspeicher nicht lesen und den Key daher nicht prüfen. Der Key gilt nicht als ungültig; mit Backoff erneut versuchen und Retry-After beachten. |
| 504 api_error | El proveedor no respondió dentro del plazo de respuesta del workload: 300 segundos salvo que el workload fije entre 10 y 600, menos si la petición envía x-sluis-upstream-timeout. error.sluis.cause es timeout. La pasarela no repitió la llamada porque el proveedor puede seguir procesándola; acorte el prompt o pida un plazo mayor antes de reenviarla. |
| 5xx api_error | Fallo del proveedor upstream tras los reintentos; el cortacircuitos desvía el tráfico de los proveedores no saludables. El cuerpo incluye el mensaje del propio proveedor junto a un objeto error.sluis que nombra la causa y cada intento realizado, con su estado upstream o su error de transporte y su duración. |
{
"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 habla la Anthropic Messages API de forma nativa. Apunte Claude Code o cualquier SDK de Anthropic a la pasarela y llame a POST /v1/messages: la petición se traduce a la forma interna, atraviesa exactamente la misma canalización Inspect → Route → Seal → Meter que cualquier otra llamada y vuelve traducida al formato de Anthropic, streaming incluido. /anthropic/v1/messages es la ruta con prefijo, sin ambigüedad, hacia el mismo handler.
Autenticación
Envíe su clave de Sluis en la cabecera x-api-key de Anthropic o en la habitual cabecera Authorization: Bearer; ambas se aceptan. La cabecera anthropic-version se lee pero nunca se exige, y solo se valida su forma. El análisis es deliberadamente abierto: campos de cuerpo desconocidos, tipos de bloque de contenido desconocidos, valores anthropic-beta desconocidos y la consulta ?beta=true que envía Claude Code se toleran en lugar de rechazarse. Los indicadores beta viajan byte a byte hacia los upstreams de la familia Anthropic y se ignoran en cualquier otro sitio.
Endpoints
| Endpoint | Propósito |
|---|---|
| POST / | Messages, en búfer o en streaming · prompts de sistema como cadena o bloques, herramientas y resultados de herramientas (incluidos los resultados de error y los bloques de imagen dentro de ellos), imágenes, bloques de documento, bloques thinking y redacted_thinking del historial y marcadores cache_control en cada posición cacheable. |
| POST / | Estimación local de tokens para la gestión de contexto del SDK: no se envía, retiene ni mide nada. |
| GET / | Descubrimiento nativo de modelos en la forma de Anthropic (data, has_more, first_id, last_id, display_name, created_at, cursores before_id/after_id/limit en la medida de lo posible), servido desde el catálogo en proceso sin tocar la base de datos. En la ruta raíz esta forma la elige la cabecera anthropic-version — sin ella obtiene la forma de OpenAI; bajo /anthropic rige siempre. Los ids siguen siendo ids provider/model invocables en Sluis y created_at es un marcador fijo, porque el catálogo no conoce la fecha de lanzamiento de cada modelo. |
| HEAD / | La sonda de calentamiento que Claude Code envía antes de una sesión · solo HEAD, respondida con 200 y cuerpo vacío. |
Enrutamiento de modelos
El formato de cable nunca limita el modelo: la entrada es un códec y el id del modelo decide la ruta. Un id desnudo como claude-sonnet-5 se resuelve por el proveedor por defecto de esta entrada — anthropic salvo que su organización lo apunte a otro, por ejemplo vertex para Claude en la UE — y después la puerta de residencia sigue decidiendo si ese destino puede servir la petición. Fije un destino explícitamente con un id prefijado por proveedor (vertex/claude-opus-4-8) o pase un alias sluis/*: una herramienta sobre el SDK de Anthropic corre entonces en cualquier proveedor conectado, dentro de su política.
Streaming
Con "stream": true la respuesta es la gramática completa de eventos de Anthropic: message_start con el recuento real de tokens de entrada, content_block_start / content_block_delta / content_block_stop por bloque, message_delta con el uso acumulado incluidos los tokens de lectura y de creación de caché, y luego message_stop. El texto llega como text_delta, los argumentos de herramienta como input_json_delta y el razonamiento como thinking_delta más signature_delta cuando el proveedor enrutado los produjo. Un evento ping sigue a message_start y se repite tras unos 15 segundos de silencio del upstream, así un turno de razonamiento largo nunca dispara el watchdog del cliente, y un fallo a mitad del flujo se recodifica como un evento error en vez de un flujo truncado. stop_sequence lleva la secuencia coincidente cuando la respuesta vino de un proveedor de la familia Anthropic, y es null en cualquier otro caso.
# 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"}Errores
Todo fallo en estas rutas usa el sobre de Anthropic — un "type": "error" de primer nivel con el tipo de error del proveedor (invalid_request_error, authentication_error, billing_error, permission_error, not_found_error, conflict_error, request_too_large, rate_limit_error, api_error, overloaded_error) — incluidos los rechazos que nunca llegan a un modelo, como una clave desconocida o un bloqueo de la protección de datos. El id de petición de la pasarela viaja en la cabecera request-id de la respuesta y en el campo request_id del cuerpo, y es el mismo id que identifica la llamada en el rastro de auditoría.
# 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…" }
Un campo de la petición se rechaza en lugar de reenviarse: mcp_servers devuelve 400 invalid_request_error. Un modelo que llame directamente a un servidor MCP externo quedaría fuera de la puerta de Sluis, así que registre el servidor en la Consola y use la pasarela MCP en /v1/mcp, donde cada llamada a herramienta se inspecciona, enruta, sella y mide.
Matriz de compatibilidad
Lo que cubren hoy las entradas nativas, endpoint por endpoint. Esta tabla es la afirmación: lo que aquí no figura como soportado está en la hoja de ruta, se rechaza a propósito o pertenece a otra superficie de producto — nunca se insinúa en silencio.
| Anthropic | Estado | Notas |
|---|---|---|
| POST / | ✓ nativo | En búfer y en streaming, herramientas, imágenes, bloques de documento, ida y vuelta del thinking y marcadores de caché de prompt. |
| POST / | ✓ estimación local | Respondido en local; no se envía nada. |
| GET / | ✓ nativo | Forma nativa de list y get desde el catálogo en proceso; negociada por cabecera en la ruta raíz, siempre activa bajo /anthropic. |
| HEAD / | ✓ | La sonda de calentamiento de Claude Code, solo HEAD. |
| Message Batches | ✗ hoja de ruta | Message Batches no está implementado; hoy el trabajo por lotes corre como llamadas en vivo normales. |
| Files | ✗ hoja de ruta | La API Files no está puenteada; los bloques de documento en línea y file_data cubren el camino habitual. |
| mcp_servers | ✗ rechazado | Rechazado con 400 en lugar de reenviado — registre el servidor y llame a la pasarela MCP de Sluis en /v1/mcp. |
| Skills · Agents · Admin | ✗ fuera de alcance | Skills, los agentes gestionados y las API de administración, uso y costes son otra superficie de producto, no compatibilidad de inferencia. |
Enrutamiento entre protocolos. Los campos propios de un proveedor sobreviven mientras la petición aterrice en un proveedor que hable el mismo protocolo: una petición de Anthropic servida por un proveedor de la familia Anthropic conserva sus bloques thinking, sus marcadores de caché, sus indicadores beta y la stop_sequence coincidente. Enrutada a otro protocolo, esos campos se descartan en el despacho — nunca un rechazo en la entrada — y la fila de auditoría registra tanto el formato de cable como el proveedor resuelto, así cualquier pérdida es atribuible. Los valores opacos nunca se reescriben: las firmas del thinking y los payloads redacted_thinking se reenvían literalmente o no se reenvían, y por eso un bloque thinking aparcado que la exploración de protección de datos marca se descarta en vez de enmascararse — un bloque reescrito llevaría una firma inválida.