Construire en toute clarté.
Une API compatible OpenAI, avec des routes de modèles publiées et un traitement des données sans détour.
Où nous en sommes
L’API est construite et testée. Tant que la première route de modèle n’est pas ouverte, les requêtes de chat renvoient 503 inference_not_configured. L’état en direct se trouve toujours sur https://undergrove.vercel.app/api/status.
Elle suit le format de l’API d’OpenAI : les SDK existants fonctionnent en changeant l’URL de base, pour le chat avec images, l’appel de fonctions et les sorties structurées, ainsi que les embeddings, la transcription et la synthèse vocale. Pour que les requêtes restent illisibles même pour notre passerelle, utilisez le mode chiffré, comme le fait l’espace de travail.
Démarrage rapide
URL de base : https://undergrove.vercel.app/v1. Listez les modèles avec GET /v1/models, puis diffusez une réponse en streaming :
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 en-têtes
- 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 réponse jusqu’ici
Passer d’OpenAI en une ligne
Undergrove parle l’API d’OpenAI : les SDK officiels fonctionnent donc tels quels. Donnez au client l’URL de base et une clé d’Undergrove, dans la ligne qui le crée, puis nommez l’un des modèles ouverts proposés ici.
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)
2 lignes changent : l’URL de base et la clé du client, et le modèle.
Ou ne changez aucun code : les deux SDK lisent OPENAI_BASE_URL et OPENAI_API_KEY quand le client est créé sans eux ; les régler sur https://undergrove.vercel.app/v1 et votre clé fait basculer une application telle quelle. Le streaming, les images, l’appel de fonctions et les sorties structurées fonctionnent comme décrit ci-dessous, pour les modèles qui les prennent en charge.
Outils pour développeurs
Des clients qui vérifient l’enclave et chiffrent sur votre propre machine. Chacun vérifie d’abord l’attestation de l’enclave Tinfoil, puis chiffre chaque requête pour cette enclave : le relais d’Undergrove la transmet sans pouvoir la lire. Ils se trouvent dans le dossier packages/ du dépôt, sont testés sur un substitut local et ne sont encore publiés sur aucun registre.
Essayer la CLI
Ce terminal rejoue la vraie commande undergrove. Sa sortie a été enregistrée avec le substitut local sur lequel les packages sont testés : verify est une vraie vérification d’une attestation Tinfoil enregistrée, models liste le catalogue actuel, et les réponses à ask sont écrites à l’avance. Aucun modèle n’est derrière cette page, et rien de ce que vous tapez ne la quitte.
Le terminal se charge quand vous y arrivez.
Applications d’exemple
Trois petites applications web dans packages/examples, chacune composée d’une page et d’un serveur Node qui détient la clé. Sans clé, elles tournent sur le substitut local avec des réponses écrites à l’avance, et rien ne quitte votre ordinateur ; avec UNDERGROVE_API_KEY définie, le même code vérifie l’enclave et chiffre chaque requête pour elle.
Un chat qui répond à partir d’un fichier de centre d’aide, diffusé vers la page en server-sent events.
npm run support-botdans packages/examples ; le code source est dans 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 });
}
}Dans votre éditeur et votre CI
- SDK Node@undergrove/sdk : chat de style OpenAI, streaming, embeddings, transcription et synthèse vocale, chiffrés par défaut.
packages/sdk-js - SDK Pythonundergrove, sur le SDK Python de Tinfoil, avec le même chiffrement par défaut.
packages/sdk-python - CLIundergrove ask, models et verify, avec la clé dans le trousseau du système. Pour les éditeurs : --events diffuse des lignes JSON, acp l’exécute comme agent et mcp comme outils.
packages/cli - VS CodeUn panneau de chat, et Explain selection, Fix this (avec Apply fix) et Write tests sur le code que vous avez sous les yeux.
packages/vscode - JetBrains IDEsUn chat dans une fenêtre d’outils et les trois mêmes actions dans IntelliJ IDEA, PyCharm et les autres, avec la clé dans le coffre de mots de passe de l’IDE.
packages/jetbrains - ZedUndergrove comme agent dans l’Agent Panel, avec /explain, /fix, /tests et /review, ou comme outils pour l’agent de Zed. Les deux passent par la CLI, qui chiffre sur votre machine.
packages/zed - Neovim:UndergroveAsk, et :UndergroveExplain sur une sélection visuelle, avec la réponse diffusée dans une fenêtre flottante.
packages/neovim - RaycastAsk Undergrove, et Ask About Selected Text dans n’importe quelle app.
packages/raycast - GitHub ActionUndergrove Review : une revue par pull request, avec des commentaires de ligne et des suggestions, et l’attestation en pied de page.
packages/github-action
Playground
Envoyez une requête de chat depuis cette page avec votre propre clé, et voyez la réponse, son utilisation et la réponse brute, avec la même requête en curl. Elle part de votre navigateur vers le /v1 de ce site, comme tout autre appel d’API, et consomme votre quota ou votre crédit comme d’habitude.
Choisissez Chiffré pour l’envoyer comme le fait le mode chiffré des SDK : le SDK de Tinfoil vérifie l’enclave dans cette page, puis scelle la requête pour elle. Le playground montre les octets qui ont réellement quitté la page, ce qui est revenu et le reçu de l’enclave pour laquelle elle a été scellée.
La même requête avec 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": "Aide-moi à explorer une idée."
}
],
"stream": true
}'Authentification
Envoyez une clé d’API Undergrove comme jeton Bearer. Les clés commencent par ugk_ et sont délivrées sur invitation pendant l’alpha. Là où les clés en libre-service sont activées, les membres de l’alpha en créent jusqu’à cinq dans les réglages de l’espace de travail, et chacune consomme le quota et le crédit de son compte. Nous ne stockons qu’un hachage à sens unique de chaque clé : une clé perdue ne peut donc pas être récupérée, seulement remplacée.
Chaque clé en libre-service peut avoir ses propres limites, définies à sa création ou plus tard dans les réglages : un plafond de dépense quotidien, au-delà duquel ses requêtes sont refusées jusqu’à minuit UTC, et une liste des modèles qu’elle peut utiliser. Une clé plafonnée compte toujours dans le quota de son compte. Les clés créées avant l’existence des limites n’en ont aucune et fonctionnent comme avant.
curl https://undergrove.vercel.app/v1/models \
-H "Authorization: Bearer $UNDERGROVE_API_KEY"Points de terminaison
| Point de terminaison | Rôle |
|---|---|
| GET /v1/models | Les modèles disponibles maintenant, chacun avec son type (chat, embedding, transcription ou synthèse vocale), sa licence, sa taille de contexte, ses prix, ses niveaux de raisonnement, sa prise en charge des images, des outils et de la recherche web, son rythme (rapide ou réfléchi) et son lieu d’exécution. |
| 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 | Vecteurs pour la recherche et la similarité, de Nomic Embed Text v1.5 (768 dimensions). Jusqu’à 256 entrées. 0,002 $ par tranche de 48 entrées ou de 67 200 caractères, selon ce qui est le plus élevé. |
| POST /v1/audio/transcriptions | Transcriptions de Whisper Large v3 Turbo. Envoi multipart jusqu’à 4,4 Mo, avec langue et prompt facultatifs. 0,01 $ par mégaoctet entamé. |
| POST /v1/audio/speech | Audio WAV de Qwen3-TTS en neuf voix : aiden, dylan, eric, ono_anna, ryan, serena, sohee, uncle_fu et vivian. Jusqu’à 4 096 caractères. 0,005 $ par tranche de 600 caractères entamée. |
| GET /api/status | État du service, limites d’utilisation et registre complet des modèles. |
Les réponses ont la forme standard chat.completion. Les flux envoient des événements chat.completion.chunk et se terminent par data: [DONE]. Les modèles de raisonnement diffusent leur raisonnement à part dans delta.reasoning_content, et le dernier fragment indique l’utilisation des jetons. Si un flux est interrompu, l’événement final est un objet d’erreur au lieu de [DONE] : une réponse partielle n’est donc jamais prise pour une réponse complète.
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"]
}'Paramètres
| Champ | Type | Par défaut | Signification |
|---|---|---|---|
model | string | Obligatoire | Un id de /v1/models. |
messages | array | Obligatoire | Jusqu’à 200 messages system, user, assistant et tool. Pour les modèles dotés de vision, les messages user peuvent inclure jusqu’à 16 parties image_url sous forme de data URL en base64 (PNG, JPEG, GIF ou WebP) ; les liens ne sont pas récupérés. Chaque image compte pour 3 000 jetons en entrée, sauf indication contraire du modèle. |
tools, tool_choice, parallel_tool_calls | array, string | object, boolean | — | Appel de fonctions pour les modèles avec tools dans /v1/models : jusqu’à 128 fonctions. Les appels diffusés arrivent sous forme de deltas tool_calls, et la réponse se termine par finish_reason tool_calls. Renvoyez les résultats sous forme de messages tool avec leur tool_call_id. |
response_format | object | — | Sorties structurées : {"type": "json_object"}, ou json_schema avec un nom et un schéma. |
stream | boolean | false | Envoyez true pour diffuser la réponse en server-sent events. |
max_tokens | integer | La limite du modèle | Accepté aussi sous le nom max_completion_tokens. Plafonné à la limite de sortie du modèle. |
temperature, top_p | number | Fixé par le modèle | Réglages d’échantillonnage : 0–2 et 0–1. |
reasoning_effort | string | Fixé par le modèle | Combien de temps un modèle de raisonnement réfléchit avant de répondre. Les valeurs acceptées varient selon le modèle et figurent dans /v1/models ; Rapide et Approfondi dans l’espace de travail utilisent les valeurs fast et thorough indiquées. |
web_search_options | object | — | Envoyez {} pour laisser le modèle chercher sur le web. Les requêtes sont filtrées pour retirer les données personnelles, et les résultats pour détecter les injections de prompt. Les sources arrivent sous forme d’annotations url_citation. Chaque requête de recherche coûte 0,05 $ de votre quota. |
stop | string | string[] | — | Une chaîne ou jusqu’à quatre chaînes. |
presence_penalty, frequency_penalty, seed | number, integer | — | Transmis lorsque la route du modèle les prend en charge. |
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"]
}
}
}]
}'Mode chiffré
L’API standard est lue en mémoire par notre passerelle pour vérifier chaque requête. Pour que les requêtes soient aussi illisibles pour Undergrove, utilisez le SDK de Tinfoil avec votre clé. Il vérifie l’attestation matérielle de l’enclave, puis chiffre chaque requête pour que seule cette enclave puisse la lire. Notre relais vérifie votre clé et votre quota, et transmet le texte chiffré.
- Votre codeLe SDK de Tinfoil vérifie l’enclave, puis chiffre chaque requête pour elle.Peut lire la requête
- Scellée pour l’enclaveRelais UndergroveVérifie la clé et l’allocation, transmet le texte chiffré. Ne peut pas le lire.Ne voit que du texte chiffré
- Toujours scelléEnclave du modèleLe seul endroit où la requête est déchiffrée et où le modèle s’exécute.Peut lire la requête
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);Le même client accède à embeddings, audio/transcriptions et audio/speech. Comme le relais ne peut pas lire ces requêtes, les modèles sont désignés par leur id enclave_model de /v1/models, les réponses reviennent au format de Tinfoil et l’usage est estimé à partir de la taille des données chiffrées. Les quotas et le crédit s’appliquent comme d’habitude.
Serveur MCP
Undergrove est aussi un serveur Model Context Protocol, à l’adresse https://undergrove.vercel.app/mcp. Chaque modèle en direct est un outil, comme ask_<model>, que des clients MCP tels que Claude Code, Cursor et Claude Desktop peuvent appeler avec votre clé d’API. Un outil prend un prompt, et en option system, max_tokens et, pour les modèles qui cherchent, web_search, puis renvoie la réponse du modèle sous forme de texte.
Un appel d’outil est une requête d’API comme une autre : la passerelle la lit en mémoire, l’envoie sur la route publiée du modèle, n’en garde aucune copie et la décompte des limites, du quota et du crédit de votre clé. Le serveur utilise HTTP en streaming, répond en JSON et ne conserve aucune session. Il parle les versions de protocole 2025-11-25, 2025-06-18 et 2025-03-26.
Claude Code
À exécuter une fois dans un terminal :
claude mcp add --transport http undergrove https://undergrove.vercel.app/mcp \
--header "Authorization: Bearer ugk_your_key"Cursor
Dans ~/.cursor/mcp.json, ou .cursor/mcp.json dans un projet :
{
"mcpServers": {
"undergrove": {
"url": "https://undergrove.vercel.app/mcp",
"headers": {
"Authorization": "Bearer ugk_your_key"
}
}
}
}Claude Desktop
Dans claude_desktop_config.json (Settings › Developer › Edit Config). Claude Desktop accède aux serveurs distants via mcp-remote, qui nécessite 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"
}
}
}
}Tout client
Ou appelez-le directement. Il parle JSON-RPC sur 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"}'Appelez ensuite l’outil d’un modèle :
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."}
}
}'Dans l’autre sens, l’espace de travail peut utiliser les outils des serveurs MCP que vous connectez dans les Réglages. Ces appels passent par le serveur d’Undergrove jusqu’au serveur connecté, en clair : utilisez des serveurs à qui vous confiez ce que reçoivent les outils.
Erreurs et limites
Les erreurs suivent le format d’OpenAI : {"error": {"message", "type", "code"}}.
| État | Codes |
|---|---|
| 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 (une clé ugk_sandbox_, à laquelle seul le stand-in local répond) |
| 403 | alpha_access_required, model_not_allowed_for_key (la clé est limitée à d’autres modèles) |
| 404 | model_not_found, webhook_not_found, thread_not_found, assistant_not_found, prompt_not_found, version_not_found |
| 409 | idempotency_in_progress (la première requête avec cet Idempotency-Key est encore en cours), version_conflict (un élément scellé a changé depuis sa lecture), id_taken |
| 413 | request_too_large (chat et transcription au-delà de 4,4 Mo, embeddings au-delà de 1 Mo, synthèse vocale au-delà de 100 Ko) |
| 422 | idempotency_key_reused (le même Idempotency-Key avec une autre requête) |
| 429 | rate_limited, budget_exhausted, capacity_exhausted, key_budget_exceeded (la clé a atteint son propre plafond quotidien ; le message indique l’heure de réinitialisation). Attendez le délai de l’en-tête Retry-After, ajoutez du crédit dans les réglages de l’espace de travail, ou relevez-y le plafond de la clé. |
| 502 | model_unavailable, stream_interrupted |
| 503 | capacity_exhausted (avec Retry-After), inference_not_configured, realtime_not_available (une route audio n’est pas attestée) |
| 504 | model_timeout |
Pendant l’alpha, chaque clé ou compte a un quota par minute, de requêtes quotidiennes et de dépense quotidienne. Les valeurs actuelles sont publiées sur /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"
}
}Savoir où vont vos données
- Votre serveurEnvoie la requête avec votre clé.Peut lire la requête
- HTTPSPasserelle UndergroveVérifie la clé et les limites, lit la requête en mémoire, n’en garde aucune copie.Peut lire la requête
- Chiffrée pour l’enclave vérifiéeEnclave du modèleL’enclave matérielle de Tinfoil exécute le modèle sur la route publiée.Peut lire la requête
La passerelle vérifie votre clé, applique les limites et ne transmet à la route du modèle que les champs ci-dessus. Elle ne garde aucune copie des prompts ni des réponses et ne les journalise pas. Ses journaux n’enregistrent que des noms de routes, des codes d’état et des durées. Les compteurs de limites de débit utilisent un hachage à sens unique de votre clé ou de votre compte et expirent sous deux jours.
L’opérateur, l’emplacement et la conservation de chaque route de modèle sont publiés dans le registre des modèles sur /api/status et sur la page de confidentialité. Les requêtes ne basculent jamais sur une autre route. Vercel, notre hébergeur, conserve les métadonnées de requête habituelles, comme les adresses IP.
Sur les routes d’informatique confidentielle, la passerelle de l’API vérifie l’attestation matérielle de l’enclave avant de se connecter et chiffre chaque requête pour que seule cette enclave puisse la lire. La passerelle elle-même lit toujours les requêtes d’API en mémoire pour les vérifier.
Données de l’espace de travail
La synchronisation, les liens partagés et les projets partagés ne stockent que du texte chiffré produit par le navigateur. Chaque schéma montre, étape par étape, qui détient la clé.
Ce que prouve la vérification
Avant tout envoi, l’espace de travail exécute dans votre navigateur le vérificateur open source de Tinfoil. Il vérifie le rapport d’attestation matérielle de l’enclave avec les clés de signature d’AMD ou d’Intel, vérifie que le code mesuré dans l’enclave correspond à une version publiée et signée par Tinfoil sur GitHub, et vérifie que la clé de chiffrement utilisée appartient à cette enclave. Sélectionnez Vérifié dans l’espace de travail pour voir chaque vérification, la version et la mesure.
Elle prouve que vos messages ne peuvent être déchiffrés que par ce matériel exécutant ce code publié. Elle ne prouve pas que le code publié est exempt de failles, ne protège pas contre les faiblesses des processeurs eux-mêmes et ne couvre pas ce que vous faites des réponses une fois arrivées dans votre navigateur. Le code de l’espace de travail est servi par Undergrove : vous nous faites donc confiance pour servir le vérificateur tel qu’il est publié ; Vérifier cette page compare le code chargé par votre navigateur avec le manifeste de build, et npm run verify:site compare le site avec votre propre build du même commit. Une évaluation indépendante est prévue.
Chaque réponse d’une enclave a un bouton Reçu. Il montre l’enclave, la version et les mesures vérifiées avant l’envoi de cette requête ainsi que la clé pour laquelle elle a été chiffrée, et les télécharge en JSON. Jusqu’à la fermeture de l’onglet, le téléchargement inclut le rapport d’attestation, pour que vous puissiez réexécuter le vérificateur dessus plus tard ; ensuite, il contient l’empreinte du rapport et l’endroit où récupérer le rapport actuel de l’enclave. Le reçu est le relevé de votre navigateur, pas une déclaration signée, et l’enclave qu’il nomme est le routeur de modèles de Tinfoil ; le routeur transmet les requêtes à l’enclave propre au modèle. Le reçu nomme aussi cette enclave, et votre navigateur tente de la vérifier de la même façon. La plupart des enclaves de modèles s’exécutent sur Intel TDX, que le vérificateur du navigateur ne sait pas encore vérifier ; dans ce cas, le reçu l’indique, et la vérification repose sur le routeur, dont le code publié vérifie chaque enclave de modèle avant de l’utiliser.
# 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/attestationRéponses vérifiables
Pour les modèles qui s’exécutent dans une enclave, chaque réponse de l’API indique quelle enclave la passerelle a vérifiée avant d’envoyer votre requête, et GET /v1/attestation renvoie le paquet d’attestation correspondant, pour que vous puissiez exécuter le vérificateur open source de Tinfoil sur les mêmes preuves.
| En-tête | Valeur |
|---|---|
x-undergrove-attestation | verified-by-gateway |
x-undergrove-enclave-host | L’enclave attestée : le routeur de modèles de Tinfoil |
x-undergrove-enclave-measurement | La mesure du code, rapprochée d’une version signée |
x-undergrove-enclave-release | L’étiquette de la version (et -release-digest, son empreinte publiée) |
x-undergrove-enclave-key | La clé publique de l’enclave pour laquelle la requête a été chiffrée |
x-undergrove-attestation-sha256 | Le SHA-256 du rapport d’attestation |
x-undergrove-attestation-url | Où récupérer le paquet : /v1/attestation?sha256=… |
Lisez-les depuis n’importe quel client. Avec le SDK Python d’OpenAI, demandez la réponse brute :
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-'Pour vérifier une réponse, ce script Node envoie une requête, récupère le paquet indiqué par ses en-têtes, le vérifie avec @tinfoilsh/verifier et contrôle que la clé, la mesure, l’enclave et l’empreinte du rapport correspondent. Le playground ci-dessus fait de même dans votre navigateur avec Le vérifier dans ce navigateur.
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)}…).`,
);
Ce que montre une vérification réussie
Qu’une enclave avec cette clé et ce code mesuré existait : du matériel d’informatique confidentielle AMD ou Intel authentique, exécutant la version publiée et signée par Tinfoil sur GitHub. Et que la passerelle déclare avoir vérifié cette enclave et chiffré votre requête avec sa clé avant de la transmettre.
Ce qu’elle ne montre pas
Que la passerelle a réellement chiffré votre requête avec cette clé. Les en-têtes sont le rapport de la passerelle, pas une déclaration signée par l’enclave, et la passerelle lit les requêtes d’API en mémoire pour les vérifier. Pour vous appuyer sur votre propre vérification, utilisez le mode chiffré, où votre client vérifie l’enclave et chiffre lui-même chaque requête. L’enclave nommée est le routeur de modèles de Tinfoil, qui transmet les requêtes à l’enclave propre au modèle.
Assistant pour site web
Ajoutez un assistant à votre propre site avec une seule balise script. Il ajoute un bouton qui ouvre un panneau de chat, auquel répond le modèle ouvert de votre choix, dans la limite d’un plafond de dépense quotidien que vous fixez. Le panneau est un cadre servi par Undergrove : votre page ne peut donc pas lire ce que les visiteurs saisissent.
- Dans l’espace de travail, ouvrez les Réglages et créez une clé publiable sous Assistant de site web : son nom, son modèle, les sites autorisés à l’utiliser et un plafond quotidien.
- Copiez le code fourni et collez-le avant
</body>sur chaque page. - Essayez-le d’abord sur la page de démonstration, une boutique fictive.
<script
src="https://undergrove.vercel.app/embed.js"
data-key="ugp_…"
data-model="<model-id>"
data-instructions="Nous sommes un atelier de poterie. Aide pour les cours, les horaires d’ouverture et la livraison."
async
></script>| Attribut | Type | Par défaut | Signification |
|---|---|---|---|
data-key | string | Obligatoire | Votre clé publiable, ugp_… |
data-model | string | Obligatoire | L’un des modèles listés par la clé. |
data-title | string | “Ask a question” | Le titre du panneau. |
data-greeting | string | — | Le premier message que voient les visiteurs. |
data-instructions | string | — | Le sujet de votre site, ce sur quoi aider et le ton. Envoyé avec chaque question ; les visiteurs peuvent le lire dans votre page. |
data-label | string | “Ask” | Le texte du bouton. |
data-position | string | right | right ou left. |
data-theme | string | auto | auto (l’appareil du visiteur), light ou dark. |
data-open | boolean | false | true ouvre le panneau au chargement de la page. |
Pourquoi une clé publiable peut figurer dans une page publique
- Elle ne fonctionne que pour le chat, et seulement depuis le cadre de l’assistant d’Undergrove. L’API la refuse partout ailleurs.
- Elle ne fonctionne que tant que le cadre est intégré par l’un des sites que vous indiquez. Le navigateur signale quelle page intègre le cadre, et le serveur le vérifie.
- Elle a toujours un plafond de dépense quotidien et une liste de modèles. Un programme hors d’un navigateur peut se faire passer pour n’importe quel site : considérez donc la clé comme publique et fixez le plafond à ce que vous acceptez de dépenser en une journée.
- Chaque visiteur a une limite de questions par minute et par jour, publiée sur /api/status. Les visiteurs sont comptés sous un hachage à sens unique et à clé de leur adresse IP, jamais l’adresse elle-même, et les comptes expirent sous deux jours.
Les réponses consomment le quota et le crédit de votre compte, comme toute clé. Pour les modèles qui s’exécutent dans une enclave, le cadre chiffre chaque message dans le navigateur du visiteur pour l’enclave vérifiée, comme le fait l’espace de travail ; les autres modèles sont joints via la passerelle, qui lit les requêtes en mémoire et n’en garde aucune copie. Votre site ne voit ni l’un ni l’autre.
Badge « Verified by Undergrove »
Un site doté d’un assistant peut afficher un petit badge qui renvoie vers la page d’Undergrove consacrée à sa clé. Cette page lit les sites et modèles de la clé sur le serveur d’Undergrove, vérifie l’enclave qui les sert dans le navigateur du visiteur, et indique ce que le badge affirme et n’affirme pas. Placez la balise là où le badge doit apparaître.
Ou, sans script, une image et un lien (badge-dark.svg pour les pages sombres) :
<script src="https://undergrove.vercel.app/badge.js" data-key="ugp_…" async></script>- Le badge est un lien. Il n’envoie aucune requête depuis votre page et ne vérifie rien lui-même ; c’est la page qu’il ouvre qui fait les vérifications.
- Il indique votre clé publiable, déjà présente dans votre page pour l’assistant. La page du badge montre les sites et modèles de la clé, jamais le compte, le plafond ni ce qui a été dépensé.
- Le lien demande au navigateur d’envoyer l’origine de votre site, pour que la page puisse dire aux visiteurs s’ils viennent de l’un des sites de la clé. Un badge copié sur un autre site le signale là-bas.
- Il se porte garant de l’assistant seulement, pas du reste de votre site.
Intégrations
La plupart des clients d’Undergrove vérifient l’attestation de l’enclave avec le vérificateur de Tinfoil, puis chiffrent chaque requête pour cette enclave : le relais la transmet sans pouvoir la lire. Chacun prend votre clé d’API et utilise les identifiants de modèles d’Undergrove. Ils ne sont pas encore sur les registres de packages ; chacun se construit depuis packages/ dans le dépôt du site.
Les clients Rust, Java et Kotlin et l’app Make utilisent plutôt l’API développeur, faute de SDK Tinfoil sur lequel s’appuyer : la passerelle lit chaque requête en mémoire, vérifie elle-même l’enclave et indique ce qu’elle a vérifié dans les en-têtes de la réponse.
| Package | Pour | Ce qu’il fait |
|---|---|---|
@undergrove/sdk | Node | Un client de style OpenAI avec streaming, outils, embeddings, transcription et synthèse vocale. |
undergrove | Python | Le client Python, avec LangChain (ChatUndergrove) et LlamaIndex (UndergroveLLM) en extras. |
Go | Go | Un client sur le vérificateur Go de Tinfoil, avec streaming et appels d’outils. |
Undergrove | Swift | Un package Swift pour macOS 14 et iOS 17, sur le vérificateur Swift de Tinfoil. |
@undergrove/ai-sdk-provider | Vercel AI SDK | generateText, streamText et les outils, avec ce qui a été vérifié dans les métadonnées de fournisseur de chaque réponse. |
@undergrove/langchain | LangChain.js | Un modèle de chat et des embeddings. |
@undergrove/react | React | Des composants et des hooks de chat, et une route serveur qui garde la clé d’API hors du navigateur. |
create-undergrove-app | Modèle de départ | npm create undergrove-app : une application de chat privée, déployée sur Vercel en deux commandes. |
@undergrove/agents | Agents | Une boucle d’appels d’outils qui note quelle enclave vérifiée a répondu à chaque étape, dans une trace que chacun peut contrôler pour y repérer des modifications, avec des enclaves épinglées. |
undergrove | Rust | Un client pour l’API développeur, avec streaming et appels d’outils, et la preuve d’attestation de la passerelle pour chaque réponse. Il ne chiffre pas lui-même les requêtes pour l’enclave. |
dev.undergrove:undergrove | Java et Kotlin | Un client pour l’API développeur, avec streaming, outils et embeddings, ainsi que des coroutines et un Flow pour Kotlin. Pas de mode chiffré : la passerelle lit les requêtes en mémoire. |
undergrove[jupyter] | Jupyter | La commande magique de cellule %%undergrove, avec les variables du notebook et les DataFrames comme contexte, et un widget d’attestation. |
@undergrove/office | Excel et Word | =UNDERGROVE.ASK() dans n’importe quelle cellule, et dans Word, résumer, réécrire ou interroger la sélection. |
@undergrove/office | Outlook | Un résumé du message ouvert, et un brouillon de réponse que vous relisez avant l’envoi. |
undergrove-obsidian | Obsidian | Interroger la note ouverte, résumer, réécrire et continuer à écrire, avec l’enclave vérifiée sous chaque réponse. Ordinateur uniquement. |
Undergrove for Alfred | Alfred | Tapez ug et une question : la réponse en grands caractères et dans le presse-papiers, chiffrée par la CLI undergrove. |
n8n-nodes-undergrove | n8n | Un nœud communautaire : interroger un modèle, créer des embeddings et lister les modèles, avec ce qui a été vérifié pour chaque élément. |
Undergrove for Zapier | Zapier | Les actions Ask a Model et Create Embeddings, chaque requête chiffrée pour l’enclave depuis le processus du Zap. |
Undergrove for Make | Make | Une app personnalisée sur l’API développeur : la passerelle vérifie l’enclave et indique ce qu’elle a vérifié dans la sortie de chaque module. |
Undergrove Enclave Pin | GitHub Actions | Une étape qui vérifie l’enclave sur le runner avec le vérificateur de Tinfoil et fait échouer le job quand ce n’est pas celle que vous avez épinglée. |
Avec le 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);Avec les composants React et le modèle de départ, les messages vont de la page à votre propre serveur, qui les scelle ; quiconque exploite ce serveur peut les lire, comme pour toute intégration côté serveur.
Tester en CI
Le stand-in sur lequel les packages sont testés répond aux clés sandbox : toute clé qui commence par ugk_sandbox_. Vos tests peuvent en utiliser une sans secret ni frais, et Undergrove lui-même les refuse avec sandbox_key. Ses réponses sont scriptées, pas celles d’un modèle. Dans GitHub Actions, une étape le lance et définit UNDERGROVE_BASE_URL et 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: 9134838da8aa4e1792566b2988820340356517c4db0c94261eb8154843e0357554ba72fcf185f950d9301fbede574c90La suite
| 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. |
|---|---|
| Suivant | Première route de modèle, connexion à l’alpha privée, limites d’utilisation partagées. |
| Construit, pas encore activé | Clés en libre-service, offres payantes et crédit prépayé pour l’usage au-delà du quota quotidien. |
| Prévu | Accès pour les équipes. |
| Nécessite une vérification | Évaluation de sécurité indépendante de l’espace de travail, du relais et de la passerelle. |