Construye con claridad.
Una API compatible con OpenAI con rutas de modelos publicadas y un tratamiento de datos claro.
Dónde estamos
La API está construida y probada. Hasta que se abra la primera ruta de modelo, las solicitudes de chat devuelven 503 inference_not_configured. El estado en vivo está siempre en https://undergrove.vercel.app/api/status.
Sigue el formato de la API de OpenAI, así que los SDK existentes funcionan cambiando la URL base: chat con imágenes, llamadas a funciones y salidas estructuradas, además de embeddings, transcripción y voz. Para que las solicitudes sean ilegibles incluso para nuestra pasarela, usa el modo cifrado, como hace el espacio de trabajo.
Inicio rápido
URL base: https://undergrove.vercel.app/v1. Lista los modelos con GET /v1/models y luego transmite una respuesta:
curl https://undergrove.vercel.app/v1/chat/completions \
-H "Authorization: Bearer $UNDERGROVE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "<model-id>",
"stream": true,
"messages": [{"role": "user", "content": "Help me explore an idea."}]
}'HTTP/2 200
9 cabeceras
- content-type
- text/event-stream; charset=utf-8
- cache-control
- no-store
- x-undergrove-route
- tinfoil
- x-undergrove-attestation
- verified-by-gateway
- x-undergrove-enclave-host
- inference.tinfoil.sh
- x-undergrove-enclave-measurement
- c0ffee12…9a7d
- x-undergrove-enclave-key
- 4f2b81e0…d3c6
- x-undergrove-attestation-sha256
- 8e41c7aa…01fb
- x-undergrove-attestation-url
- /v1/attestation?sha256=8e41c7aa…01fb
La respuesta hasta ahora
Cámbiate desde OpenAI con una línea
Undergrove habla la API de OpenAI, así que los SDK oficiales funcionan tal cual. Da al cliente la URL base de Undergrove y una clave de Undergrove, en la línea que lo crea, y luego indica uno de los modelos abiertos que se ofrecen aquí.
import os
from openai import OpenAI
client = OpenAI(base_url="https://undergrove.vercel.app/v1", api_key=os.environ["UNDERGROVE_API_KEY"])
response = client.chat.completions.create(
model="<model-id>",
messages=[{"role": "user", "content": "Summarise this contract in three points."}],
)
print(response.choices[0].message.content)
Cambian 2 líneas: la URL base y la clave del cliente, y el modelo.
O no cambies nada de código: ambos SDK leen OPENAI_BASE_URL y OPENAI_API_KEY cuando el cliente se crea sin ellos, así que poner en esas variables https://undergrove.vercel.app/v1 y tu clave cambia una aplicación tal como está. El streaming, las imágenes, las llamadas a funciones y las salidas estructuradas funcionan como se describe abajo, en los modelos que los admiten.
Herramientas para desarrolladores
Clientes que verifican el enclave y cifran en tu propia máquina. Cada uno comprueba primero la atestación del enclave de Tinfoil y luego cifra cada solicitud para ese enclave, de modo que el retransmisor de Undergrove la reenvía sin poder leerla. Están en la carpeta packages/ del repositorio, se prueban contra un sustituto local y aún no se han publicado en ningún registro.
Prueba la CLI
Este terminal reproduce el comando real undergrove. Su salida se grabó contra el sustituto local con el que se prueban los paquetes: verify es una comprobación real de una atestación de Tinfoil grabada, models lista el catálogo actual y las respuestas a ask están guionizadas. No hay ningún modelo detrás de esta página, y nada de lo que escribas sale de ella.
El terminal se carga cuando llegas a él.
Apps de ejemplo
Tres pequeñas apps web en packages/examples, cada una con una página y un servidor Node que guarda la clave. Sin clave, se ejecutan contra el sustituto local con respuestas guionizadas, y nada sale de tu ordenador; con UNDERGROVE_API_KEY definida, el mismo código verifica el enclave y cifra cada solicitud para él.
Un chat que responde a partir de un archivo del centro de ayuda, transmitido a la página como server-sent events.
npm run support-boten packages/examples; el código fuente está en packages/examples/support-bot
const SYSTEM = `You are the support assistant for Acme Bikes.
Answer only from the help centre below. Quote prices and times exactly.
When it doesn't cover a question, say so and offer the contact options.
Keep answers short: two to four sentences, or a short list.
${knowledge}`;
// Streams one answer. `history` is the conversation so far, as
// [{ role: "user" | "assistant", content }].
export async function answer(undergrove, history, send) {
const stream = await undergrove.chat.completions.create({
model,
stream: true,
max_tokens: 600,
messages: [{ role: "system", content: SYSTEM }, ...history.slice(-12)],
});
for await (const chunk of stream) {
const text = chunk.choices[0]?.delta?.content;
if (text) send({ text });
}
}En tu editor y en CI
- SDK de Node@undergrove/sdk: chat al estilo de OpenAI, streaming, embeddings, transcripción y voz, cifrado por defecto.
packages/sdk-js - SDK de Pythonundergrove, sobre el SDK de Python de Tinfoil, con el mismo cifrado por defecto.
packages/sdk-python - CLIundergrove ask, models y verify, con la clave en el llavero del sistema. Para editores: --events transmite líneas JSON, acp lo ejecuta como agente y mcp como herramientas.
packages/cli - VS CodeUn panel de chat, y Explain selection, Fix this (con Apply fix) y Write tests sobre el código que tienes delante.
packages/vscode - JetBrains IDEsUn chat en una ventana de herramientas y las mismas tres acciones en IntelliJ IDEA, PyCharm y los demás, con la clave en el almacén de contraseñas del IDE.
packages/jetbrains - ZedUndergrove como agente en el Agent Panel, con /explain, /fix, /tests y /review, o como herramientas para el agente propio de Zed. Ambos ejecutan la CLI, que cifra en tu máquina.
packages/zed - Neovim:UndergroveAsk, y :UndergroveExplain sobre una selección visual, con la respuesta llegando en tiempo real a una ventana flotante.
packages/neovim - RaycastAsk Undergrove y Ask About Selected Text en cualquier app.
packages/raycast - GitHub ActionUndergrove Review: una revisión por pull request, con comentarios en las líneas y sugerencias, y la atestación en su pie.
packages/github-action
Playground
Envía una solicitud de chat desde esta página con tu propia clave y mira la respuesta, su uso y la respuesta sin procesar, con la misma solicitud en curl. Va de tu navegador al /v1 de este sitio, como cualquier otra llamada a la API, y gasta tu asignación o tu crédito como siempre.
Elige Cifrado para enviarla como lo hace el modo cifrado de los SDK: el SDK de Tinfoil verifica el enclave en esta página y luego sella la solicitud para él. El playground muestra los bytes que salieron realmente de la página, lo que volvió y el comprobante del enclave para el que se selló.
La misma solicitud con curl
curl --no-buffer https://undergrove.vercel.app/v1/chat/completions \
-H "Authorization: Bearer $UNDERGROVE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "<model-id>",
"messages": [
{
"role": "user",
"content": "Ayúdame a explorar una idea."
}
],
"stream": true
}'Autenticación
Envía una clave de API de Undergrove como token Bearer. Las claves empiezan por ugk_ y se emiten por invitación durante la alfa. Donde las claves de autoservicio están activadas, los miembros de la alfa crean hasta cinco en los ajustes del espacio de trabajo, y cada una gasta la asignación y el crédito de su cuenta. Solo guardamos un hash unidireccional de cada clave, así que una clave perdida no se puede recuperar, solo sustituir.
Cada clave de autoservicio puede tener sus propios límites, fijados al crearla o más tarde en los ajustes: un límite de gasto diario, tras el cual sus solicitudes se rechazan hasta medianoche UTC, y una lista de los modelos que puede usar. Una clave con límite sigue contando contra la asignación de su cuenta. Las claves creadas antes de que existieran los límites no tienen ninguno y siguen funcionando como antes.
curl https://undergrove.vercel.app/v1/models \
-H "Authorization: Bearer $UNDERGROVE_API_KEY"Endpoints
| Endpoint | Finalidad |
|---|---|
| GET /v1/models | Los modelos disponibles ahora, cada uno con su tipo (chat, embedding, transcripción o voz), su licencia, tamaño de contexto, precios, niveles de razonamiento, compatibilidad con imágenes, herramientas y búsqueda web, ritmo (rápido o reflexivo) y dónde se ejecuta. |
| POST /v1/chat/completions | Chat, streamed or as a single response, with images, function calling and structured outputs where the model supports them. |
| POST /v1/embeddings | Vectores para búsqueda y similitud de Nomic Embed Text v1.5 (768 dimensiones). Hasta 256 entradas. 0,002 $ por cada 48 entradas o 67.200 caracteres, lo que sea mayor. |
| POST /v1/audio/transcriptions | Transcripciones de Whisper Large v3 Turbo. Subida multipart de hasta 4,4 MB, con idioma y prompt opcionales. 0,01 $ por megabyte empezado. |
| POST /v1/audio/speech | Audio WAV de Qwen3-TTS con nueve voces: aiden, dylan, eric, ono_anna, ryan, serena, sohee, uncle_fu y vivian. Hasta 4096 caracteres. 0,005 $ por cada 600 caracteres empezados. |
| GET /api/status | Estado del servicio, límites de uso y el registro de modelos completo. |
Las respuestas usan la forma estándar chat.completion. Los streams envían eventos chat.completion.chunk y terminan con data: [DONE]. Los modelos de razonamiento transmiten su razonamiento aparte en delta.reasoning_content, y el último fragmento informa del uso de tokens. Si un stream se interrumpe, el evento final es un objeto de error en lugar de [DONE], así que una respuesta parcial nunca se confunde con una completa.
curl https://undergrove.vercel.app/v1/embeddings \
-H "Authorization: Bearer $UNDERGROVE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "undergrove/nomic-embed-text",
"input": ["Where is the signed contract?", "Payment terms"]
}'Parámetros
| Campo | Tipo | Predeterminado | Significado |
|---|---|---|---|
model | string | Obligatorio | Un id de /v1/models. |
messages | array | Obligatorio | Hasta 200 mensajes system, user, assistant y tool. En los modelos con visión, los mensajes user pueden incluir hasta 16 partes image_url como data URL en base64 (PNG, JPEG, GIF o WebP); los enlaces no se descargan. Cada imagen cuenta como 3000 tokens de entrada salvo que el modelo indique otra cosa. |
tools, tool_choice, parallel_tool_calls | array, string | object, boolean | — | Llamadas a funciones para los modelos con tools en /v1/models: hasta 128 funciones. Las llamadas transmitidas llegan como deltas tool_calls, y la respuesta termina con finish_reason tool_calls. Devuelve los resultados como mensajes tool con su tool_call_id. |
response_format | object | — | Salidas estructuradas: {"type": "json_object"}, o json_schema con un nombre y un esquema. |
stream | boolean | false | Envía true para transmitir la respuesta como server-sent events. |
max_tokens | integer | El límite del modelo | También se acepta como max_completion_tokens. Se limita al límite de salida del modelo. |
temperature, top_p | number | Lo fija el modelo | Controles de muestreo: 0–2 y 0–1. |
reasoning_effort | string | Lo fija el modelo | Cuánto piensa un modelo de razonamiento antes de responder. Los valores aceptados varían según el modelo y aparecen en /v1/models; Rápido y Minucioso del espacio de trabajo usan los valores fast y thorough de la lista. |
web_search_options | object | — | Envía {} para que el modelo busque en la web. Las consultas se filtran para quitar datos personales y los resultados para detectar inyección de prompts. Las fuentes llegan como anotaciones url_citation. Cada solicitud con búsqueda cuesta 0,05 $ de tu asignación. |
stop | string | string[] | — | Una cadena o hasta cuatro cadenas. |
presence_penalty, frequency_penalty, seed | number, integer | — | Se transmiten cuando la ruta del modelo los admite. |
curl https://undergrove.vercel.app/v1/chat/completions \
-H "Authorization: Bearer $UNDERGROVE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "<model-id>",
"max_tokens": 500,
"messages": [{"role": "user", "content": "Is it raining in Lisbon?"}],
"tools": [{
"type": "function",
"function": {
"name": "get_weather",
"description": "Current weather for a city",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"]
}
}
}]
}'Modo cifrado
La API estándar la lee nuestra pasarela en memoria para comprobar cada solicitud. Para que las solicitudes también sean ilegibles para Undergrove, usa el SDK de Tinfoil con tu clave. Verifica la atestación de hardware del enclave y luego cifra cada solicitud para que solo ese enclave pueda leerla. Nuestro retransmisor comprueba tu clave y tu asignación y reenvía el texto cifrado.
- Tu códigoEl SDK de Tinfoil verifica el enclave y luego cifra cada solicitud para él.Puede leer la solicitud
- Sellada para el enclaveRetransmisor de UndergroveComprueba la clave y la asignación y reenvía el texto cifrado. No puede leerlo.Solo ve texto cifrado
- Aún selladoEnclave del modeloEl único lugar donde se descifra la solicitud y se ejecuta el modelo.Puede leer la solicitud
import { SecureClient } from "tinfoil"; // npm install tinfoil
const RELAY = "https://undergrove.vercel.app/api/enclave";
const client = new SecureClient({
baseURL: `${RELAY}/v1/`,
attestationBundleURL: RELAY,
});
await client.ready(); // verifies the enclave; throws if it can't
const response = await client.fetch("chat/completions", {
method: "POST",
headers: {
authorization: `Bearer ${process.env.UNDERGROVE_API_KEY}`,
"content-type": "application/json",
},
body: JSON.stringify({
model: "gemma4-31b", // enclave_model in /v1/models
max_tokens: 1000,
messages: [{ role: "user", content: "Summarise this contract." }],
}),
});
console.log((await response.json()).choices[0].message.content);El mismo cliente llega a embeddings, audio/transcriptions y audio/speech. Como el retransmisor no puede leer estas solicitudes, los modelos se nombran por su id enclave_model de /v1/models, las respuestas vuelven en el formato de Tinfoil y el uso se estima a partir del tamaño de los datos cifrados. Las asignaciones y el crédito se aplican como siempre.
Servidor MCP
Undergrove también es un servidor Model Context Protocol, en https://undergrove.vercel.app/mcp. Cada modelo en vivo es una herramienta, como ask_<model>, que clientes MCP como Claude Code, Cursor y Claude Desktop pueden llamar con tu clave de API. Una herramienta recibe un prompt, y opcionalmente system, max_tokens y, para los modelos que buscan, web_search, y devuelve la respuesta del modelo como texto.
Una llamada a una herramienta es una solicitud a la API como cualquier otra: la pasarela la lee en memoria, la envía por la ruta publicada del modelo, no guarda ninguna copia y la cuenta contra los límites, la asignación y el crédito de tu clave. El servidor usa HTTP en streaming, responde en JSON y no mantiene sesiones. Habla las versiones del protocolo 2025-11-25, 2025-06-18 y 2025-03-26.
Claude Code
Ejecútalo una vez en un terminal:
claude mcp add --transport http undergrove https://undergrove.vercel.app/mcp \
--header "Authorization: Bearer ugk_your_key"Cursor
En ~/.cursor/mcp.json, o en .cursor/mcp.json dentro de un proyecto:
{
"mcpServers": {
"undergrove": {
"url": "https://undergrove.vercel.app/mcp",
"headers": {
"Authorization": "Bearer ugk_your_key"
}
}
}
}Claude Desktop
En claude_desktop_config.json (Settings › Developer › Edit Config). Claude Desktop llega a servidores remotos a través de mcp-remote, que necesita Node.js:
{
"mcpServers": {
"undergrove": {
"command": "npx",
"args": [
"-y",
"mcp-remote",
"https://undergrove.vercel.app/mcp",
"--header",
"Authorization:${AUTH_HEADER}"
],
"env": {
"AUTH_HEADER": "Bearer ugk_your_key"
}
}
}
}Cualquier cliente
O llámalo directamente. Habla JSON-RPC sobre HTTP en streaming:
curl https://undergrove.vercel.app/mcp \
-H "Authorization: Bearer ugk_your_key" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc": "2.0", "id": 1, "method": "tools/list"}'Después, llama a la herramienta de un modelo:
curl https://undergrove.vercel.app/mcp \
-H "Authorization: Bearer $UNDERGROVE_API_KEY" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "ask_<model>",
"arguments": {"prompt": "Name three uses for a sealed envelope."}
}
}'En sentido inverso, el espacio de trabajo puede usar herramientas de servidores MCP que conectes en los Ajustes. Esas llamadas pasan por el servidor de Undergrove hasta el servidor que conectaste, en texto plano, así que usa servidores en los que confíes con lo que reciben las herramientas.
Errores y límites
Los errores usan el formato de OpenAI: {"error": {"message", "type", "code"}}.
| Estado | Códigos |
|---|---|
| 400 | invalid_request, invalid_json, unsupported_parameter, unsupported_content, context_length_exceeded, model_rejected_request, unsupported_api_version, async_needs_webhook, async_stream, invalid_webhook_url |
| 401 | authentication_required, invalid_api_key, invalid_session, sandbox_key (una clave ugk_sandbox_, que solo responde el stand-in local) |
| 403 | alpha_access_required, model_not_allowed_for_key (la clave está limitada a otros modelos) |
| 404 | model_not_found, webhook_not_found, thread_not_found, assistant_not_found, prompt_not_found, version_not_found |
| 409 | idempotency_in_progress (la primera solicitud con este Idempotency-Key sigue en curso), version_conflict (un elemento sellado cambió desde que se leyó), id_taken |
| 413 | request_too_large (chat y transcripción por encima de 4,4 MB, embeddings por encima de 1 MB, voz por encima de 100 KB) |
| 422 | idempotency_key_reused (el mismo Idempotency-Key con otra solicitud) |
| 429 | rate_limited, budget_exhausted, capacity_exhausted, key_budget_exceeded (la clave alcanzó su propio límite diario; el mensaje indica la hora de reinicio). Espera lo que indique la cabecera Retry-After, añade crédito en los ajustes del espacio de trabajo o sube allí el límite de la clave. |
| 502 | model_unavailable, stream_interrupted |
| 503 | capacity_exhausted (con Retry-After), inference_not_configured, realtime_not_available (una ruta de audio no está atestiguada) |
| 504 | model_timeout |
Durante la alfa, cada clave o cuenta tiene una asignación por minuto, de solicitudes diarias y de gasto diario. Los valores actuales se publican en /api/status.
HTTP/2 429
retry-after: 41
{
"error": {
"message": "You’re sending messages quickly. Please wait a moment.",
"type": "rate_limit_error",
"code": "rate_limited"
}
}Sabe adónde van tus datos
- Tu servidorEnvía la solicitud con tu clave.Puede leer la solicitud
- HTTPSPasarela de UndergroveComprueba la clave y los límites, lee la solicitud en memoria y no guarda copia.Puede leer la solicitud
- Cifrada para el enclave verificadoEnclave del modeloEl enclave de hardware de Tinfoil ejecuta el modelo en la ruta publicada.Puede leer la solicitud
La pasarela comprueba tu clave, aplica los límites y reenvía solo los campos de arriba a la ruta del modelo. No guarda ninguna copia de los prompts ni de las respuestas y no los registra. Sus registros recogen solo nombres de rutas, códigos de estado y tiempos. Los contadores de límites de frecuencia usan un hash unidireccional de tu clave o cuenta y caducan en un plazo de dos días.
El operador, la ubicación y la conservación de cada ruta de modelo se publican en el registro de modelos en /api/status y en la página de privacidad. Las solicitudes nunca pasan a otra ruta como alternativa. Vercel, nuestro proveedor de alojamiento, guarda los metadatos habituales de las solicitudes, como las direcciones IP.
En las rutas de computación confidencial, la pasarela de la API verifica la atestación de hardware del enclave antes de conectarse y cifra cada solicitud para que solo ese enclave pueda leerla. La propia pasarela sigue leyendo las solicitudes a la API en memoria para comprobarlas.
Datos del espacio de trabajo
La sincronización, los enlaces compartidos y los proyectos compartidos solo guardan texto cifrado creado por el navegador. Cada diagrama muestra, paso a paso, quién tiene la clave.
Lo que demuestra la verificación
Antes de enviar nada, el espacio de trabajo ejecuta en tu navegador el verificador de código abierto de Tinfoil. Comprueba el informe de atestación de hardware del enclave frente a las claves de firma de AMD o Intel, comprueba que el código medido en el enclave coincide con una versión que Tinfoil publicó y firmó en GitHub, y comprueba que la clave de cifrado que usa pertenece a ese enclave. Selecciona Verificado en el espacio de trabajo para ver cada comprobación, la versión y la medición.
Demuestra que tus mensajes solo pueden descifrarse en ese hardware ejecutando ese código publicado. No demuestra que el código publicado esté libre de fallos, no protege frente a debilidades de los propios procesadores ni cubre lo que hagas con las respuestas cuando llegan a tu navegador. El código del espacio de trabajo lo sirve Undergrove, así que confías en que sirvamos el verificador tal como se publicó; Verificar esta página compara el código que cargó tu navegador con el manifiesto de la compilación, y npm run verify:site compara el sitio con tu propia compilación del mismo commit. Está prevista una evaluación independiente.
Cada respuesta de un enclave tiene un botón Comprobante. Muestra el enclave, la versión y las mediciones verificadas antes de enviar esa solicitud y la clave para la que se cifró, y los descarga como JSON. Hasta que se cierra la pestaña, la descarga incluye el informe de atestación, para que puedas volver a ejecutar el verificador sobre él más tarde; después, contiene el hash del informe y dónde descargar el actual del enclave. El comprobante es el registro de tu navegador, no una declaración firmada, y el enclave que indica es el enrutador de modelos de Tinfoil; el enrutador pasa las solicitudes al enclave propio del modelo. El comprobante también indica ese enclave, y tu navegador intenta verificarlo del mismo modo. La mayoría de los enclaves de modelos se ejecutan en Intel TDX, que el verificador del navegador aún no puede comprobar; en esos casos el comprobante lo dice, y la comprobación descansa en el enrutador, cuyo código publicado verifica cada enclave de modelo antes de usarlo.
# The attestation bundle the gateway verified, what it checked
# in it, and the steps to check it again yourself
curl https://undergrove.vercel.app/v1/attestationRespuestas verificables
En los modelos que se ejecutan en un enclave, cada respuesta de la API dice qué enclave verificó la pasarela antes de enviar tu solicitud, y GET /v1/attestation devuelve el paquete de atestación que la respalda, para que puedas ejecutar el verificador de código abierto de Tinfoil sobre las mismas pruebas.
| Cabecera | Valor |
|---|---|
x-undergrove-attestation | verified-by-gateway |
x-undergrove-enclave-host | El enclave atestado: el enrutador de modelos de Tinfoil |
x-undergrove-enclave-measurement | La medición del código, cotejada con una versión firmada |
x-undergrove-enclave-release | La etiqueta de la versión (y -release-digest, su resumen publicado) |
x-undergrove-enclave-key | La clave pública del enclave para la que se cifró la solicitud |
x-undergrove-attestation-sha256 | El SHA-256 del informe de atestación |
x-undergrove-attestation-url | Dónde descargar el paquete: /v1/attestation?sha256=… |
Léelas desde cualquier cliente. Con el SDK de Python de OpenAI, pide la respuesta sin procesar:
curl -si https://undergrove.vercel.app/v1/chat/completions \
-H "Authorization: Bearer $UNDERGROVE_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model": "<model-id>", "messages": [{"role": "user", "content": "Say hello."}]}' \
| grep -i '^x-undergrove-'Para comprobar una respuesta, este script de Node envía una solicitud, descarga el paquete que indican sus cabeceras, lo verifica con @tinfoilsh/verifier y comprueba que la clave, la medición, el enclave y el hash del informe coinciden. El playground de arriba hace lo mismo en tu navegador con Comprobarlo en este navegador.
verify-response.mjs
// Checks the attestation evidence on an Undergrove API response yourself.
//
// npm install @tinfoilsh/verifier
// UNDERGROVE_API_KEY=ugk_… node verify-response.mjs
import { createHash } from "node:crypto";
import { Verifier } from "@tinfoilsh/verifier";
const base = process.env.UNDERGROVE_BASE_URL || "https://undergrove.vercel.app/v1";
const model = process.env.UNDERGROVE_MODEL || "undergrove/gpt-oss-120b";
// 1. Each response from an attested route names the enclave the gateway
// verified and encrypted the request to.
const response = await fetch(`${base}/chat/completions`, {
method: "POST",
headers: {
authorization: `Bearer ${process.env.UNDERGROVE_API_KEY}`,
"content-type": "application/json",
},
body: JSON.stringify({
model,
max_tokens: 50,
messages: [{ role: "user", content: "Say hello." }],
}),
});
if (!response.ok) throw new Error(`The API answered ${response.status}.`);
const header = (name) => response.headers.get(`x-undergrove-${name}`);
const claim = {
host: header("enclave-host"),
key: header("enclave-key"),
measurement: header("enclave-measurement"),
report: header("attestation-sha256"),
};
if (!claim.key) throw new Error("This response carries no attestation evidence.");
// 2. Fetch the attestation bundle it names. Without a report hash, the
// route's current bundle (its key must still match).
const query = claim.report ? `?sha256=${claim.report}` : "";
const { bundle } = await (await fetch(`${base}/attestation${query}`)).json();
// 3. Verify the bundle with Tinfoil's open-source verifier: the chip
// maker's signature on the report, the release's signed build record
// from GitHub, and the enclave's certificate. It throws if any fails.
const verifier = new Verifier({ configRepo: "tinfoilsh/confidential-model-router" });
const attested = await verifier.verifyBundle(bundle);
const verified = verifier.getVerificationDocument();
// 4. The verified bundle must attest exactly what the response claimed.
const { format, body } = bundle.enclaveAttestationReport;
const hash = createHash("sha256").update(format + body).digest("hex");
const checks = [
["report hash", !claim.report || hash === claim.report],
["encryption key", attested.hpkePublicKey === claim.key],
["code measurement", verified.codeFingerprint === claim.measurement],
["enclave host", verified.enclaveHost === claim.host],
];
for (const [name, ok] of checks) if (!ok) throw new Error(`The ${name} doesn’t match the response.`);
console.log(
`Verified: ${verified.enclaveHost} runs ${verified.configRepo} ${verified.releaseTag || ""},` +
` and its attested key is the one this response names (${claim.key.slice(0, 16)}…).`,
);
Qué demuestra una comprobación superada
Que existía un enclave con esa clave y ese código medido: hardware genuino de computación confidencial de AMD o Intel, ejecutando la versión que Tinfoil publicó y firmó en GitHub. Y que la pasarela afirma que verificó ese enclave y cifró tu solicitud con su clave antes de reenviarla.
Qué no demuestra
Que la pasarela de verdad cifrara tu solicitud con esa clave. Las cabeceras son el informe de la pasarela, no una declaración firmada por el enclave, y la pasarela lee las solicitudes a la API en memoria para comprobarlas. Para basarte en tu propia verificación, usa el modo cifrado, en el que tu cliente comprueba el enclave y cifra cada solicitud por sí mismo. El enclave indicado es el enrutador de modelos de Tinfoil, que pasa las solicitudes al enclave propio del modelo.
Asistente para sitios web
Pon un asistente en tu propio sitio web con una etiqueta script. Añade un botón que abre un panel de chat, respondido por el modelo abierto que elijas, dentro de un límite de gasto diario que tú fijas. El panel es un marco servido por Undergrove, así que tu página no puede leer lo que escriben los visitantes.
- En el espacio de trabajo, abre Ajustes y crea una clave publicable en Asistente para sitios web: su nombre, el modelo, los sitios web que pueden usarla y un límite diario.
- Copia el código que te da y pégalo antes de
</body>en cada página. - Pruébalo primero en la página de demostración, una tienda inventada.
<script
src="https://undergrove.vercel.app/embed.js"
data-key="ugp_…"
data-model="<model-id>"
data-instructions="Somos un taller de cerámica. Ayuda con las clases, el horario y los envíos."
async
></script>| Atributo | Tipo | Predeterminado | Significado |
|---|---|---|---|
data-key | string | Obligatorio | Tu clave publicable, ugp_… |
data-model | string | Obligatorio | Uno de los modelos que indica la clave. |
data-title | string | “Ask a question” | El título del panel. |
data-greeting | string | — | El primer mensaje que ven los visitantes. |
data-instructions | string | — | De qué trata tu sitio web, con qué ayudar y el tono. Se envía con cada pregunta; los visitantes pueden leerlo en tu página. |
data-label | string | “Ask” | El texto del botón. |
data-position | string | right | right o left. |
data-theme | string | auto | auto (el dispositivo del visitante), light o dark. |
data-open | boolean | false | true abre el panel al cargar la página. |
Por qué una clave publicable puede estar en una página pública
- Solo funciona para el chat, y solo desde el marco del asistente de Undergrove. La API la rechaza en cualquier otro sitio.
- Solo funciona mientras el marco esté incrustado en uno de los sitios web que indiques. El navegador informa de qué página incrusta el marco, y el servidor lo comprueba.
- Siempre tiene un límite de gasto diario y una lista de modelos. Un programa fuera de un navegador puede hacerse pasar por cualquier sitio web, así que trata la clave como pública y fija el límite en lo que estés dispuesto a gastar en un día.
- Cada visitante tiene un límite de preguntas por minuto y por día, publicado en /api/status. Los visitantes se cuentan bajo un hash unidireccional con clave de su dirección IP, nunca la dirección, y los recuentos caducan en un plazo de dos días.
Las respuestas gastan la asignación y el crédito de tu cuenta, como cualquier clave. En los modelos que se ejecutan en un enclave, el marco cifra cada mensaje en el navegador del visitante para el enclave verificado, como hace el espacio de trabajo; a los demás modelos se llega a través de la pasarela, que lee las solicitudes en memoria y no guarda ninguna copia. Tu sitio web no ve ni lo uno ni lo otro.
Insignia «Verified by Undergrove»
Un sitio web con asistente puede mostrar una pequeña insignia que enlaza a la página de Undergrove para su clave. Esa página lee del servidor de Undergrove los sitios web y modelos de la clave, verifica el enclave que hay detrás en el navegador del visitante y explica lo que la insignia afirma y lo que no. Pon la etiqueta donde deba aparecer la insignia.
O, sin script, una imagen y un enlace (badge-dark.svg para páginas oscuras):
<script src="https://undergrove.vercel.app/badge.js" data-key="ugp_…" async></script>- La insignia es un enlace. No hace solicitudes desde tu página ni comprueba nada por sí misma; la comprobación la hace la página que abre.
- Indica tu clave publicable, que ya está en tu página para el asistente. La página de la insignia muestra los sitios web y modelos de la clave, y nunca la cuenta, el límite ni lo que se ha gastado.
- El enlace pide al navegador que envíe el origen de tu sitio web, para que la página pueda decir a los visitantes si vienen de uno de los sitios web de la clave. Una insignia copiada en otro sitio web lo indica allí.
- Responde solo del asistente, no del resto de tu sitio web.
Integraciones
La mayoría de los clientes propios de Undergrove comprueban la atestación de la enclave con el verificador de Tinfoil y luego cifran cada solicitud para esa enclave, así que el retransmisor la pasa sin poder leerla. Cada uno usa tu clave de API y los identificadores de modelo de Undergrove. Aún no están en los registros de paquetes; cada uno se compila desde packages/ en el repositorio del sitio.
Los clientes de Rust, Java y Kotlin y la app de Make usan en su lugar la API para desarrolladores, porque no hay un SDK de Tinfoil sobre el que construirlos: la pasarela lee cada solicitud en memoria, verifica ella misma la enclave e indica lo que verificó en los encabezados de la respuesta.
| Paquete | Para | Qué hace |
|---|---|---|
@undergrove/sdk | Node | Un cliente al estilo de OpenAI con streaming, herramientas, embeddings, transcripción y voz. |
undergrove | Python | El cliente de Python, con LangChain (ChatUndergrove) y LlamaIndex (UndergroveLLM) como extras. |
Go | Go | Un cliente sobre el verificador de Go de Tinfoil, con streaming y llamadas a herramientas. |
Undergrove | Swift | Un paquete de Swift para macOS 14 e iOS 17, sobre el verificador de Swift de Tinfoil. |
@undergrove/ai-sdk-provider | Vercel AI SDK | generateText, streamText y herramientas, con lo que se verificó en los metadatos del proveedor de cada respuesta. |
@undergrove/langchain | LangChain.js | Un modelo de chat y embeddings. |
@undergrove/react | React | Componentes y hooks de chat, y una ruta de servidor que mantiene la clave de API fuera del navegador. |
create-undergrove-app | Plantilla inicial | npm create undergrove-app: una app de chat privada que se despliega en Vercel con dos comandos. |
@undergrove/agents | Agentes | Un bucle de llamadas a herramientas que registra qué enclave verificado respondió en cada paso, en un registro que cualquiera puede comprobar en busca de cambios, con enclaves fijados. |
undergrove | Rust | Un cliente para la API para desarrolladores, con streaming y llamadas a herramientas, y la evidencia de atestación de la pasarela en cada respuesta. No cifra él mismo las solicitudes para la enclave. |
dev.undergrove:undergrove | Java y Kotlin | Un cliente para la API para desarrolladores, con streaming, herramientas y embeddings, y corrutinas y un Flow para Kotlin. No es el modo cifrado: la pasarela lee las solicitudes en memoria. |
undergrove[jupyter] | Jupyter | La magia de celda %%undergrove, con variables del cuaderno y DataFrames como contexto, y un widget de atestación. |
@undergrove/office | Excel y Word | =UNDERGROVE.ASK() en cualquier celda, y resumir, reescribir o preguntar sobre la selección en Word. |
@undergrove/office | Outlook | Un resumen del mensaje abierto y un borrador de respuesta que revisas antes de enviarlo. |
undergrove-obsidian | Obsidian | Pregunta sobre la nota abierta, resume, reescribe y sigue escribiendo, con la enclave verificada bajo cada respuesta. Solo en escritorio. |
Undergrove for Alfred | Alfred | Escribe ug y una pregunta: la respuesta en texto grande y en el portapapeles, cifrada a través de la CLI undergrove. |
n8n-nodes-undergrove | n8n | Un nodo de la comunidad: preguntar a un modelo, crear embeddings y listar modelos, con lo que se verificó en cada elemento. |
Undergrove for Zapier | Zapier | Acciones Ask a Model y Create Embeddings, con cada solicitud cifrada para la enclave desde el proceso del Zap. |
Undergrove for Make | Make | Una app personalizada sobre la API para desarrolladores: la pasarela verifica la enclave e indica lo que verificó en la salida de cada módulo. |
Undergrove Enclave Pin | GitHub Actions | Un paso que verifica la enclave en el runner con el verificador de Tinfoil y hace fallar el trabajo cuando no es la que fijaste. |
Con el Vercel AI SDK:
import { streamText } from "ai";
import { undergrove } from "@undergrove/ai-sdk-provider";
const result = streamText({
model: undergrove("undergrove/gemma-4-31b"),
prompt: "Summarise this contract.",
});
for await (const text of result.textStream) process.stdout.write(text);Con los componentes de React y la plantilla inicial, los mensajes van de la página a tu propio servidor, que los sella; quien gestione ese servidor puede leerlos, como en cualquier integración del lado del servidor.
Pruebas en CI
El stand-in con el que se prueban los paquetes responde a claves sandbox: cualquier clave que empiece por ugk_sandbox_. Tus pruebas pueden usar una sin secretos ni cargos, y Undergrove en sí las rechaza con sandbox_key. Sus respuestas son guionizadas, no de un modelo. En GitHub Actions, un paso lo inicia y define UNDERGROVE_BASE_URL y UNDERGROVE_API_KEY:
- uses: ekempinski/undergrove/packages/github-action/sandbox@main
- run: npm test # UNDERGROVE_API_KEY=ugk_sandbox_ci
# And before a deploy: fail if the enclave isn’t the one you pinned.
- uses: ekempinski/undergrove/packages/github-action/pin@main
with:
measurement: 9134838da8aa4e1792566b2988820340356517c4db0c94261eb8154843e0357554ba72fcf185f950d9301fbede574c90Lo siguiente
| Disponible | Workspace with encryption from your browser to the enclave, streaming gateway, OpenAI-compatible API with images, function calling, structured outputs, embeddings, transcription and speech, encrypted API mode, web search, documents, folders and codebases, images, audio transcripts, read-aloud, search across long documents, voice input, model register, saved conversations and export on your device. |
|---|---|
| Siguiente | Primera ruta de modelo, inicio de sesión de la alfa privada, límites de uso compartidos. |
| Construido, aún sin activar | Claves de autoservicio, planes de pago y crédito prepago para el uso que supere la asignación diaria. |
| Previsto | Acceso para equipos. |
| Requiere verificación | Evaluación de seguridad independiente del espacio de trabajo, el retransmisor y la pasarela. |