DOCUMENTATION DÉVELOPPEURS / BIENTÔT DISPONIBLE

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 :

request.shShell
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."}]
  }'
Exemple de réponseUn exemple ; valeurs abrégées

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.

    Gardée uniquement dans la mémoire de cette page et envoyée seulement à https://undergrove.vercel.app/v1. Actualiser ou quitter la page l’efface. Une clé avec un plafond quotidien limite ce qu’une erreur peut coûter.

    Chemin

    Le mode chiffré nécessite un modèle qui s’exécute dans une enclave ; ce n’est pas le cas de celui-ci.

    L’API ne répond pas encore : tant que la première route de modèle n’est pas ouverte, les requêtes renvoient 503 inference_not_configured. Vous pouvez quand même composer une requête et la copier en curl.

    Messages

    Ctrl ou ⌘ et Entrée dans un message l’envoie aussi.

    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.

    models.shShell
    curl https://undergrove.vercel.app/v1/models \
      -H "Authorization: Bearer $UNDERGROVE_API_KEY"

    Points de terminaison

    Point de terminaisonRôle
    GET /v1/modelsLes 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/completionsChat, streamed or as a single response, with images, function calling and structured outputs where the model supports them.
    POST /v1/embeddingsVecteurs 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/transcriptionsTranscriptions 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/speechAudio 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.

    embeddings.shShell
    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

    ChampTypePar défautSignification
    modelstringObligatoireUn id de /v1/models.
    messagesarrayObligatoireJusqu’à 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_callsarray, 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_formatobject—Sorties structurées : {"type": "json_object"}, ou json_schema avec un nom et un schéma.
    streambooleanfalseEnvoyez true pour diffuser la réponse en server-sent events.
    max_tokensintegerLa limite du modèleAccepté aussi sous le nom max_completion_tokens. Plafonné à la limite de sortie du modèle.
    temperature, top_pnumberFixé par le modèleRéglages d’échantillonnage : 0–2 et 0–1.
    reasoning_effortstringFixé par le modèleCombien 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_optionsobject—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.
    stopstring | string[]—Une chaîne ou jusqu’à quatre chaînes.
    presence_penalty, frequency_penalty, seednumber, integer—Transmis lorsque la route du modèle les prend en charge.
    tools.shShell
    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é.

    1. Votre codeLe SDK de Tinfoil vérifie l’enclave, puis chiffre chaque requête pour elle.Peut lire la requête
    2. 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é
    3. 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
    Mode chiffré : votre code vérifie lui-même l’enclave, et la requête reste scellée jusqu’à ce qu’elle y soit.
    encrypted.mjsJavaScript
    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 :

    terminalShell
    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 :

    mcp.jsonJSON
    {
      "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 :

    claude_desktop_config.jsonJSON
    {
      "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 :

    tools-list.shShell
    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 :

    tools-call.shShell
    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"}}.

    ÉtatCodes
    400invalid_request, invalid_json, unsupported_parameter, unsupported_content, context_length_exceeded, model_rejected_request, unsupported_api_version, async_needs_webhook, async_stream, invalid_webhook_url
    401authentication_required, invalid_api_key, invalid_session, sandbox_key (une clé ugk_sandbox_, à laquelle seul le stand-in local répond)
    403alpha_access_required, model_not_allowed_for_key (la clé est limitée à d’autres modèles)
    404model_not_found, webhook_not_found, thread_not_found, assistant_not_found, prompt_not_found, version_not_found
    409idempotency_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
    413request_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)
    422idempotency_key_reused (le même Idempotency-Key avec une autre requête)
    429rate_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é.
    502model_unavailable, stream_interrupted
    503capacity_exhausted (avec Retry-After), inference_not_configured, realtime_not_available (une route audio n’est pas attestée)
    504model_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.

    429.jsonJSON
    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

    1. Votre serveurEnvoie la requête avec votre clé.Peut lire la requête
    2. 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
    3. 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
    L’API standard : la passerelle lit chaque requête en mémoire pour la vérifier, puis l’envoie chiffrée à l’enclave qu’elle a vérifiée.

    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.

    attestation.shShell
    # 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/attestation

    Ré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êteValeur
    x-undergrove-attestationverified-by-gateway
    x-undergrove-enclave-hostL’enclave attestée : le routeur de modèles de Tinfoil
    x-undergrove-enclave-measurementLa mesure du code, rapprochée d’une version signée
    x-undergrove-enclave-releaseL’étiquette de la version (et -release-digest, son empreinte publiée)
    x-undergrove-enclave-keyLa clé publique de l’enclave pour laquelle la requête a été chiffrée
    x-undergrove-attestation-sha256Le SHA-256 du rapport d’attestation
    x-undergrove-attestation-urlOù 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 :

    headers.shShell
    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
    verify-response.mjsJavaScript
    // 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.

    1. 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.
    2. Copiez le code fourni et collez-le avant </body> sur chaque page.
    3. Essayez-le d’abord sur la page de démonstration, une boutique fictive.
    index.htmlHTML
    <script
      src="https://undergrove.vercel.app/embed.js"
      data-key="ugp_…"
      data-model="&lt;model-id&gt;"
      data-instructions="Nous sommes un atelier de poterie. Aide pour les cours, les horaires d’ouverture et la livraison."
      async
    ></script>
    AttributTypePar défautSignification
    data-keystringObligatoireVotre clé publiable, ugp_…
    data-modelstringObligatoireL’un des modèles listés par la clé.
    data-titlestring“Ask a question”Le titre du panneau.
    data-greetingstring—Le premier message que voient les visiteurs.
    data-instructionsstring—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-labelstring“Ask”Le texte du bouton.
    data-positionstringrightright ou left.
    data-themestringautoauto (l’appareil du visiteur), light ou dark.
    data-openbooleanfalsetrue 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) :

    badge.htmlHTML
    <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.

    PackagePourCe qu’il fait
    @undergrove/sdkNodeUn client de style OpenAI avec streaming, outils, embeddings, transcription et synthèse vocale.
    undergrovePythonLe client Python, avec LangChain (ChatUndergrove) et LlamaIndex (UndergroveLLM) en extras.
    GoGoUn client sur le vérificateur Go de Tinfoil, avec streaming et appels d’outils.
    UndergroveSwiftUn package Swift pour macOS 14 et iOS 17, sur le vérificateur Swift de Tinfoil.
    @undergrove/ai-sdk-providerVercel AI SDKgenerateText, streamText et les outils, avec ce qui a été vérifié dans les métadonnées de fournisseur de chaque réponse.
    @undergrove/langchainLangChain.jsUn modèle de chat et des embeddings.
    @undergrove/reactReactDes composants et des hooks de chat, et une route serveur qui garde la clé d’API hors du navigateur.
    create-undergrove-appModèle de départnpm create undergrove-app : une application de chat privée, déployée sur Vercel en deux commandes.
    @undergrove/agentsAgentsUne 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.
    undergroveRustUn 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:undergroveJava et KotlinUn 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]JupyterLa commande magique de cellule %%undergrove, avec les variables du notebook et les DataFrames comme contexte, et un widget d’attestation.
    @undergrove/officeExcel et Word=UNDERGROVE.ASK() dans n’importe quelle cellule, et dans Word, résumer, réécrire ou interroger la sélection.
    @undergrove/officeOutlookUn résumé du message ouvert, et un brouillon de réponse que vous relisez avant l’envoi.
    undergrove-obsidianObsidianInterroger la note ouverte, résumer, réécrire et continuer à écrire, avec l’enclave vérifiée sous chaque réponse. Ordinateur uniquement.
    Undergrove for AlfredAlfredTapez ug et une question : la réponse en grands caractères et dans le presse-papiers, chiffrée par la CLI undergrove.
    n8n-nodes-undergroven8nUn 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 ZapierZapierLes actions Ask a Model et Create Embeddings, chaque requête chiffrée pour l’enclave depuis le processus du Zap.
    Undergrove for MakeMakeUne 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 PinGitHub ActionsUne é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 :

    summarise.mjsJavaScript
    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 :

    .github/workflows/test.ymlYAML
    - 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: 9134838da8aa4e1792566b2988820340356517c4db0c94261eb8154843e0357554ba72fcf185f950d9301fbede574c90

    La suite

    DisponibleWorkspace 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.
    SuivantPremiè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évuAccè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.