תיעוד למפתחים / ייפתח בקרוב

לבנות בבהירות.

API תואם OpenAI, עם מסלולי מודלים מפורסמים וטיפול פשוט וברור בנתונים.

איפה אנחנו עומדים

ה־API בנוי ונבדק. עד שייפתח מסלול המודל הראשון, בקשות צ׳אט מחזירות 503 inference_not_configured. המצב העדכני תמיד זמין ב־https://undergrove.vercel.app/api/status.

הוא עוקב אחרי הפורמט של OpenAI API, כך שערכות SDK קיימות עובדות אחרי שינוי כתובת הבסיס: צ׳אט עם תמונות, קריאה לפונקציות ופלטים מובנים, וגם embeddings, תמלול ודיבור. כדי שהבקשות יישארו בלתי קריאות אפילו לשער שלנו, השתמשו במצב מוצפן, כמו שסביבת העבודה עושה.

התחלה מהירה

כתובת בסיס: https://undergrove.vercel.app/v1. מציגים את המודלים עם GET /v1/models, ואז מזרימים תשובה:

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."}]
  }'
דוגמה לתגובהדוגמה; הערכים מקוצרים

HTTP/2 200

9 כותרות
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

    התשובה עד עכשיו

    מעבר מ־OpenAI בשורה אחת

    Undergrove מדבר את OpenAI API, כך שערכות ה־SDK הרשמיות עובדות כמו שהן. תנו ללקוח את כתובת הבסיס של Undergrove ומפתח Undergrove, בשורה האחת שיוצרת אותו, ואז ציינו אחד מהמודלים הפתוחים שמוצעים כאן.

    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)
    

    שתי שורות משתנות: כתובת הבסיס והמפתח של הלקוח, והמודל.

    או בלי לשנות קוד בכלל: שתי ערכות ה־SDK קוראות את OPENAI_BASE_URL ואת OPENAI_API_KEY כשהלקוח נוצר בלעדיהם, כך שהגדרתם ל־https://undergrove.vercel.app/v1 ולמפתח שלכם מעבירה אפליקציה כמו שהיא. הזרמה, תמונות, קריאה לפונקציות ופלטים מובנים עובדים כמתואר למטה, במודלים שתומכים בהם.

    כלים למפתחים

    לקוחות שמאמתים את המובלעת ומצפינים במחשב שלכם. כל אחד מהם בודק קודם את האטסטציה של מובלעת Tinfoil, ואז מצפין כל בקשה אל המובלעת הזו, כך שהממסר של Undergrove מעביר אותה הלאה בלי יכולת לקרוא אותה. הם נמצאים בתיקייה packages/ במאגר, נבדקים מול תחליף מקומי, ועדיין לא פורסמו באף מאגר חבילות.

    נסו את ה־CLI

    הטרמינל הזה משחזר את הפקודה האמיתית undergrove. הפלט שלו הוקלט מול התחליף המקומי שהחבילות נבדקות מולו: verify היא בדיקה אמיתית של אטסטציה מוקלטת של Tinfoil, models מציגה את הקטלוג הנוכחי, והתשובות ל־ask כתובות מראש. אין מודל מאחורי הדף הזה, ושום דבר שמקלידים לא יוצא ממנו.

    הטרמינל נטען כשמגיעים אליו.

    אפליקציות לדוגמה

    שלוש אפליקציות אינטרנט קטנות ב־packages/examples, כל אחת דף ושרת Node שמחזיק את המפתח. בלי מפתח הן רצות מול התחליף המקומי עם תשובות כתובות מראש, ושום דבר לא יוצא מהמחשב שלכם; כש־UNDERGROVE_API_KEY מוגדר, אותו קוד מאמת את המובלעת ומצפין אליה כל בקשה.

    צ׳אט שעונה מתוך קובץ של מרכז עזרה, ומוזרם לדף כאירועים שנשלחים מהשרת.

    npm run support-botב־packages/examples; קוד המקור נמצא ב־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 });
      }
    }

    בעורך וב־CI

    • SDK ל־Node@undergrove/sdk: צ׳אט בסגנון OpenAI, הזרמה, embeddings, תמלול ודיבור, מוצפן כברירת מחדל.packages/sdk-js
    • SDK ל־Pythonundergrove, מבוסס על ה־SDK של Tinfoil ל־Python, עם אותה ברירת מחדל מוצפנת.packages/sdk-python
    • CLIundergrove ask, models ו־verify, עם המפתח במחזיק המפתחות של המערכת. לעורכים: --events מזרים שורות JSON, acp מריץ אותו כסוכן ו־mcp ככלים.packages/cli
    • VS Codeחלונית צ׳אט, ו־Explain selection, Fix this (עם Apply fix) ו־Write tests על הקוד שמולכם.packages/vscode
    • JetBrains IDEsצ׳אט בחלון כלי ואותן שלוש פעולות ב־IntelliJ IDEA, ב־PyCharm ובשאר, עם המפתח בכספת הסיסמאות של ה־IDE.packages/jetbrains
    • ZedUndergrove כסוכן ב־Agent Panel, עם /explain, /fix, /tests ו־/review, או ככלים לסוכן של Zed עצמו. שתי האפשרויות מריצות את ה־CLI, שמצפין במחשב שלכם.packages/zed
    • Neovim:UndergroveAsk, ו־:UndergroveExplain על בחירה במצב visual, עם התשובה מוזרמת לחלון צף.packages/neovim
    • RaycastAsk Undergrove, ו־Ask About Selected Text בכל אפליקציה.packages/raycast
    • GitHub ActionUndergrove Review: סקירה אחת לכל pull request, עם הערות על שורות והצעות, והאטסטציה בתחתית שלה.packages/github-action

    סביבת ניסויים

    אפשר לשלוח בקשת צ׳אט מהדף הזה עם המפתח שלכם, ולראות את התשובה, את השימוש ואת התגובה הגולמית, יחד עם אותה בקשה כפקודת curl. היא עוברת מהדפדפן שלכם אל /v1 של האתר הזה, כמו כל קריאת API אחרת, ומנצלת את המכסה או הקרדיט שלכם כרגיל.

    בחרו מוצפן כדי לשלוח אותה כמו שהמצב המוצפן של ערכות ה־SDK עושה: ה־SDK של Tinfoil מאמת את המובלעת בדף הזה, ואז אוטם אליה את הבקשה. סביבת הניסויים מציגה את הבייטים שבאמת יצאו מהדף, את מה שחזר, ואת הקבלה של המובלעת שאליה הבקשה נאטמה.

    נשמר רק בזיכרון של הדף הזה ונשלח רק אל https://undergrove.vercel.app/v1. טעינה מחדש של הדף או יציאה ממנו מוחקות אותו. מפתח עם תקרה יומית מגביל את המחיר של טעות.

    נתיב

    מצב מוצפן דורש מודל שרץ במובלעת; המודל הזה לא רץ במובלעת.

    ה־API עדיין לא עונה: עד שייפתח מסלול המודל הראשון, בקשות מחזירות 503 inference_not_configured. עדיין אפשר לבנות בקשה ולהעתיק אותה כפקודת curl.

    הודעות

    גם Ctrl או ⌘ ו־Enter בהודעה שולחים אותה.

    אותה בקשה עם 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": "תעזור לי לחקור רעיון."
          }
        ],
        "stream": true
      }'

    אימות זהות

    שלחו מפתח API של Undergrove כ־Bearer token. מפתחות מתחילים ב־ugk_ וניתנים בהזמנה בתקופת האלפא. כשמפתחות בשירות עצמי מופעלים, חברי האלפא יוצרים עד חמישה מפתחות בהגדרות של סביבת העבודה, וכל אחד מהם מנצל את המכסה והקרדיט של החשבון שלו. אנחנו שומרים רק גיבוב חד־כיווני של כל מפתח, ולכן אי אפשר לשחזר מפתח שאבד, רק להחליף אותו.

    לכל מפתח בשירות עצמי יכולות להיות מגבלות משלו, שנקבעות כשהוא נוצר או אחר כך בהגדרות: תקרת הוצאה יומית, שאחריה הבקשות שלו נדחות עד חצות UTC, ורשימה של המודלים שמותר לו להשתמש בהם. מפתח עם תקרה עדיין נספר במכסה של החשבון שלו. למפתחות שנוצרו לפני שהיו מגבלות אין מגבלות, והם ממשיכים לעבוד כמו קודם.

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

    נקודות קצה

    נקודת קצהמטרה
    GET /v1/modelsהמודלים הזמינים עכשיו, כל אחד עם סוג (צ׳אט, embedding, תמלול או דיבור), הרישיון שלו, גודל ההקשר, מחירים, רמות חשיבה, תמיכה בתמונות, בכלים ובחיפוש באינטרנט, קצב (מהיר או שקול), והמקום שבו הוא רץ.
    POST /v1/chat/completionsChat, streamed or as a single response, with images, function calling and structured outputs where the model supports them.
    POST /v1/embeddingsוקטורים לחיפוש ולדמיון מ־Nomic Embed Text v1.5 (768 ממדים). עד 256 קלטים. $0.002 לכל 48 קלטים או 67,200 תווים, הגבוה מביניהם.
    POST /v1/audio/transcriptionsתמלולים מ־Whisper Large v3 Turbo. העלאה מרובת חלקים (multipart) של עד 4.4 MB, עם שפה ופרומפט אופציונליים. $0.01 לכל מגה־בייט שהתחיל.
    POST /v1/audio/speechשמע WAV מ־Qwen3-TTS בתשעה קולות: aiden, dylan, eric, ono_anna, ryan, serena, sohee, uncle_fu ו־vivian. עד 4,096 תווים. $0.005 לכל 600 תווים שהתחילו.
    GET /api/statusמצב השירות, מגבלות השימוש ומרשם המודלים המלא.

    תגובות משתמשות במבנה הרגיל של chat.completion. זרמים שולחים אירועי chat.completion.chunk ומסתיימים ב־data: [DONE]. מודלי חשיבה מזרימים את החשיבה שלהם בנפרד ב־delta.reasoning_content, והמקטע האחרון מדווח על השימוש בטוקנים. אם זרם נקטע, האירוע האחרון הוא אובייקט שגיאה במקום [DONE], כך שתשובה חלקית אף פעם לא נחשבת בטעות לתשובה מלאה.

    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"]
      }'

    פרמטרים

    שדהסוגברירת מחדלמשמעות
    modelstringחובהמזהה מתוך /v1/models.
    messagesarrayחובהעד 200 הודעות system, user, assistant ו־tool. במודלים עם ראייה, הודעות user יכולות לכלול עד 16 חלקי image_url ככתובות data URL ב־base64 (PNG, JPEG, GIF או WebP); קישורים לא נשלפים. כל תמונה נחשבת 3,000 טוקני קלט, אלא אם המודל מדווח אחרת.
    tools, tool_choice, parallel_tool_callsarray, string | object, boolean—קריאה לפונקציות במודלים עם tools ב־/v1/models: עד 128 פונקציות. קריאות מוזרמות מגיעות כ־deltas של tool_calls, והתשובה מסתיימת ב־finish_reason tool_calls. שלחו את התוצאות בחזרה כהודעות tool עם ה־tool_call_id שלהן.
    response_formatobject—פלטים מובנים: {"type": "json_object"}, או json_schema עם שם וסכמה.
    streambooleanfalseשלחו true כדי להזרים את התשובה כאירועים שנשלחים מהשרת.
    max_tokensintegerהמגבלה של המודלמתקבל גם בשם max_completion_tokens. מוגבל למגבלת הפלט של המודל.
    temperature, top_pnumberנקבע על ידי המודלבקרות דגימה: 0–2 ו־0–1.
    reasoning_effortstringנקבע על ידי המודלכמה זמן מודל חשיבה חושב לפני שהוא עונה. הערכים המקובלים משתנים ממודל למודל ומפורטים ב־/v1/models; האפשרויות מהיר ויסודי בסביבת העבודה משתמשות בערכי fast ו־thorough שמפורטים שם.
    web_search_optionsobject—שלחו {} כדי לאפשר למודל לחפש באינטרנט. השאילתות מסוננות מפרטים אישיים, והתוצאות נבדקות מפני הזרקת פרומפטים. המקורות מגיעים כהערות url_citation. כל בקשת חיפוש עולה $0.05 מהמכסה שלכם.
    stopstring | string[]—מחרוזת או עד ארבע מחרוזות.
    presence_penalty, frequency_penalty, seednumber, integer—מועברים הלאה כשמסלול המודל תומך בהם.
    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"]
            }
          }
        }]
      }'

    מצב מוצפן

    את ה־API הרגיל השער שלנו קורא בזיכרון כדי לבדוק כל בקשה. כדי שהבקשות יישארו בלתי קריאות גם ל־Undergrove, השתמשו ב־SDK של Tinfoil עם המפתח שלכם. הוא מאמת את אטסטציית החומרה של המובלעת, ואז מצפין כל בקשה כך שרק המובלעת הזו יכולה לקרוא אותה. הממסר שלנו בודק את המפתח ואת המכסה שלכם ומעביר הלאה את הטקסט המוצפן.

    1. הקוד שלךה־SDK של Tinfoil מאמת את המובלעת, ואז מצפין אליה כל בקשה.יכול לקרוא את הבקשה
    2. אטום עבור המובלעתהממסר של Undergroveבודק את המפתח ואת המכסה, מעביר את הטקסט המוצפן הלאה. לא יכול לקרוא אותו.רואה רק טקסט מוצפן
    3. עדיין אטוםמובלעת המודלהמקום היחיד שבו הבקשה מפוענחת והמודל רץ.יכול לקרוא את הבקשה
    מצב מוצפן: הקוד שלכם מאמת את המובלעת בעצמו, והבקשה נשארת אטומה עד שהיא בתוכה.
    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);

    אותו לקוח מגיע גם ל־embeddings, ל־audio/transcriptions ול־audio/speech. מכיוון שהממסר לא יכול לקרוא את הבקשות האלה, המודלים נקראים לפי מזהה ה־enclave_model שלהם מ־/v1/models, התשובות חוזרות בפורמט של Tinfoil, והשימוש מוערך לפי גודל הנתונים המוצפנים. המכסות והקרדיט חלים כרגיל.

    שרת MCP

    Undergrove הוא גם שרת Model Context Protocol, בכתובת https://undergrove.vercel.app/mcp. כל מודל חי הוא כלי, כמו ask_<model>, שלקוחות MCP כמו Claude Code, Cursor ו־Claude Desktop יכולים לקרוא לו עם מפתח ה־API שלכם. כלי מקבל prompt, ובאופן אופציונלי גם system ו־max_tokens, ועבור מודלים שמחפשים גם web_search, ומחזיר את התשובה של המודל כטקסט.

    קריאה לכלי היא בקשת API כמו כל בקשה אחרת: השער קורא אותה בזיכרון, שולח אותה במסלול שפורסם של המודל, לא שומר עותק, ומחשב אותה מול המגבלות, המכסה והקרדיט של המפתח שלכם. השרת משתמש ב־streamable HTTP, עונה ב־JSON ולא שומר סשנים. הוא תומך בגרסאות הפרוטוקול 2025-11-25, 2025-06-18 ו־2025-03-26.

    Claude Code

    מריצים פעם אחת בטרמינל:

    terminalShell
    claude mcp add --transport http undergrove https://undergrove.vercel.app/mcp \
      --header "Authorization: Bearer ugk_your_key"

    Cursor

    בקובץ ~/.cursor/mcp.json, או ב־.cursor/mcp.json בתוך פרויקט:

    mcp.jsonJSON
    {
      "mcpServers": {
        "undergrove": {
          "url": "https://undergrove.vercel.app/mcp",
          "headers": {
            "Authorization": "Bearer ugk_your_key"
          }
        }
      }
    }

    Claude Desktop

    בקובץ claude_desktop_config.json (Settings › Developer › Edit Config). Claude Desktop מתחבר לשרתים מרוחקים דרך mcp-remote, שדורש 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"
          }
        }
      }
    }

    כל לקוח

    או לקרוא לו ישירות. הוא מדבר JSON-RPC על גבי HTTP בהזרמה (streamable HTTP):

    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"}'

    אחר כך אפשר לקרוא לכלי של מודל:

    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."}
        }
      }'

    בכיוון ההפוך, סביבת העבודה יכולה להשתמש בכלים משרתי MCP שמחברים בהגדרות. הקריאות האלה עוברות דרך השרת של Undergrove אל השרת שחיברתם, כטקסט גלוי, ולכן כדאי להשתמש רק בשרתים שסומכים עליהם עם מה שהכלים מקבלים.

    שגיאות ומגבלות

    שגיאות משתמשות בפורמט של OpenAI: {"error": {"message", "type", "code"}}.

    סטטוסקודים
    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 (מפתח ugk_sandbox_, שרק התחליף המקומי עונה לו)
    403alpha_access_required, model_not_allowed_for_key (המפתח מוגבל למודלים אחרים)
    404model_not_found, webhook_not_found, thread_not_found, assistant_not_found, prompt_not_found, version_not_found
    409idempotency_in_progress (הבקשה הראשונה עם ה־Idempotency-Key הזה עדיין רצה), version_conflict (פריט אטום השתנה מאז שנקרא), id_taken
    413request_too_large (צ׳אט ותמלול מעל 4.4 MB, embeddings מעל 1 MB, דיבור מעל 100 KB)
    422idempotency_key_reused (אותו Idempotency-Key עם בקשה אחרת)
    429rate_limited, budget_exhausted, capacity_exhausted, key_budget_exceeded (המפתח הגיע לתקרה היומית שלו; ההודעה מציינת את מועד האיפוס). המתינו לפי הכותרת Retry-After, הוסיפו קרדיט בהגדרות של סביבת העבודה, או הגדילו שם את התקרה של המפתח.
    502model_unavailable, stream_interrupted
    503capacity_exhausted (עם Retry-After), inference_not_configured, realtime_not_available (מסלול שמע לא עבר אטסטציה)
    504model_timeout

    בתקופת האלפא, לכל מפתח או חשבון יש מכסת בקשות לדקה, מכסת בקשות יומית ומכסת הוצאה יומית. הערכים הנוכחיים מפורסמים ב־/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"
      }
    }

    לדעת לאן הנתונים שלכם הולכים

    1. השרת שלךשולח את הבקשה עם המפתח שלכם.יכול לקרוא את הבקשה
    2. HTTPSהשער של Undergroveבודק את המפתח ואת המגבלות, קורא את הבקשה בזיכרון, לא שומר עותק.יכול לקרוא את הבקשה
    3. מוצפנת אל המובלעת המאומתתמובלעת המודלמובלעת החומרה של Tinfoil מריצה את המודל במסלול שפורסם.יכול לקרוא את הבקשה
    ה־API הרגיל: השער קורא כל בקשה בזיכרון כדי לבדוק אותה, ואז שולח אותה מוצפנת אל המובלעת שהוא אימת.

    השער בודק את המפתח שלכם, מחיל מגבלות, ומעביר למסלול המודל רק את השדות שלמעלה. הוא לא שומר עותק של פרומפטים או תשובות ולא רושם אותם ביומן. היומנים שלו מתעדים רק שמות מסלולים, קודי סטטוס ותזמונים. מוני מגבלת הקצב משתמשים בגיבוב חד־כיווני של המפתח או החשבון שלכם ופגים תוך יומיים.

    המפעיל, המיקום ומדיניות השמירה של כל מסלול מודל מפורסמים במרשם המודלים ב־/api/status ובדף הפרטיות. בקשות אף פעם לא עוברות למסלול אחר כגיבוי. Vercel, המארחת שלנו, שומרת מטא־נתונים רגילים של בקשות, כמו כתובות IP.

    במסלולי מחשוב חסוי, שער ה־API מאמת את אטסטציית החומרה של המובלעת לפני ההתחברות ומצפין כל בקשה כך שרק המובלעת הזו יכולה לקרוא אותה. השער עצמו עדיין קורא בקשות API בזיכרון כדי לבדוק אותן.

    נתוני סביבת העבודה

    סנכרון, קישורים משותפים ופרויקטים משותפים שומרים רק טקסט מוצפן שהדפדפן יצר. כל תרשים מראה, צעד אחר צעד, למי יש את המפתח.

    מה האימות מוכיח

    לפני שהיא שולחת משהו, סביבת העבודה מריצה בדפדפן שלכם את המאמת בקוד פתוח של Tinfoil. הוא בודק את דוח אטסטציית החומרה של המובלעת מול מפתחות החתימה של AMD או של Intel, בודק שהקוד שנמדד במובלעת תואם לגרסה ש־Tinfoil פרסמה וחתמה ב־GitHub, ובודק שמפתח ההצפנה שהוא משתמש בו שייך למובלעת הזו. בחרו מאומת בסביבת העבודה כדי לראות כל בדיקה, את הגרסה ואת המדידה.

    זה מוכיח שההודעות שלכם יכולות להיות מפוענחות רק על ידי החומרה הזו כשהיא מריצה את הקוד שפורסם. זה לא מוכיח שהקוד שפורסם נקי מפגמים, לא מגן מפני חולשות במעבדים עצמם, ולא מכסה את מה שאתם עושים בתשובות אחרי שהן מגיעות לדפדפן שלכם. הקוד של סביבת העבודה מוגש על ידי Undergrove, כך שאתם סומכים עלינו שנגיש את המאמת כפי שפורסם; אימות הדף הזה משווה את הקוד שהדפדפן שלכם טען למניפסט של ה־build, ו־npm run verify:site משווה את האתר ל־build שאתם בונים בעצמכם מאותו commit. מתוכננת הערכה עצמאית.

    לכל תשובה ממובלעת יש לחצן קבלה. הוא מציג את המובלעת, הגרסה והמדידות שאומתו לפני שהבקשה נשלחה ואת המפתח שאליו היא הוצפנה, ומוריד אותם כ־JSON. עד שהלשונית נסגרת, ההורדה כוללת את דוח האטסטציה, כך שאפשר להריץ עליו את המאמת שוב מאוחר יותר; אחרי כן, יש בה את הגיבוב של הדוח ואת המקום שממנו אפשר להביא את הדוח הנוכחי של המובלעת. הקבלה היא תיעוד של הדפדפן שלכם, לא הצהרה חתומה, והמובלעת שמופיעה בה היא נתב המודלים של Tinfoil; הנתב מעביר את הבקשות הלאה למובלעת של המודל עצמו. הקבלה מציינת גם את המובלעת הזו, והדפדפן שלכם מנסה לאמת אותה באותה דרך. רוב מובלעות המודלים רצות על Intel TDX, שהמאמת בדפדפן עדיין לא יכול לבדוק; עבורן הקבלה מציינת זאת, והבדיקה נשענת על הנתב, שהקוד שפורסם שלו מאמת כל מובלעת מודל לפני שהוא משתמש בה.

    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

    תגובות שאפשר לאמת

    במודלים שרצים במובלעת, כל תגובת API מציינת איזו מובלעת השער אימת לפני ששלח את הבקשה שלכם, ו־GET /v1/attestation מחזיר את חבילת האטסטציה שמאחוריה, כך שאפשר להריץ את המאמת בקוד פתוח של Tinfoil על אותן ראיות.

    כותרתערך
    x-undergrove-attestationverified-by-gateway
    x-undergrove-enclave-hostהמובלעת שעברה אטסטציה: נתב המודלים של Tinfoil
    x-undergrove-enclave-measurementמדידת הקוד, מותאמת לגרסה חתומה
    x-undergrove-enclave-releaseתג הגרסה (וגם -release-digest, ה־digest שפורסם שלה)
    x-undergrove-enclave-keyהמפתח הציבורי של המובלעת שאליו הבקשה הוצפנה
    x-undergrove-attestation-sha256ה־SHA-256 של דוח האטסטציה
    x-undergrove-attestation-urlמאיפה להביא את החבילה: /v1/attestation?sha256=…

    אפשר לקרוא אותן מכל לקוח. עם ה־SDK של OpenAI ל־Python, בקשו את התגובה הגולמית:

    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-'

    כדי לבדוק תגובה, סקריפט ה־Node הזה שולח בקשה, מביא את החבילה שהכותרות שלה מציינות, מאמת אותה עם @tinfoilsh/verifier, ובודק שהמפתח, המדידה, המובלעת והגיבוב של הדוח תואמים כולם. סביבת הניסויים שלמעלה עושה את אותו הדבר בדפדפן שלכם עם בדיקה בדפדפן הזה.

    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)}…).`,
    );
    

    מה בדיקה שעוברת מראה

    שהתקיימה מובלעת עם המפתח הזה והקוד הנמדד הזה: חומרת מחשוב חסוי אמיתית של AMD או של Intel, שמריצה את הגרסה ש־Tinfoil פרסמה וחתמה ב־GitHub. ושהשער מצהיר שהוא אימת את המובלעת הזו והצפין את הבקשה שלכם למפתח שלה לפני שהעביר אותה.

    מה היא לא מראה

    שהשער באמת הצפין את הבקשה שלכם למפתח הזה. הכותרות הן הדוח של השער, לא הצהרה חתומה על ידי המובלעת, והשער קורא בקשות API בזיכרון כדי לבדוק אותן. כדי להסתמך במקום זאת על האימות שלכם, השתמשו במצב מוצפן, שבו הלקוח שלכם בודק את המובלעת ומצפין כל בקשה בעצמו. המובלעת שמצוינת היא נתב המודלים של Tinfoil, שמעביר את הבקשות הלאה למובלעת של המודל עצמו.

    עוזר לאתר

    הוסיפו עוזר לאתר שלכם עם תג script אחד. הוא מוסיף לחצן שפותח חלונית צ׳אט, שעליה עונה המודל הפתוח שבוחרים, במסגרת תקרת הוצאה יומית שאתם קובעים. החלונית היא מסגרת (frame) שמוגשת על ידי Undergrove, כך שהדף שלכם לא יכול לקרוא את מה שמבקרים מקלידים.

    1. בסביבת העבודה, פתחו את הגדרות וצרו מפתח שניתן לפרסום תחת עוזר לאתר: השם שלו, המודל, האתרים שמותר להם להשתמש בו ותקרה יומית.
    2. העתיקו את הקוד שמתקבל והדביקו אותו לפני </body> בכל דף.
    3. נסו אותו קודם בדף ההדגמה, חנות מומצאת.
    index.htmlHTML
    <script
      src="https://undergrove.vercel.app/embed.js"
      data-key="ugp_…"
      data-model="&lt;model-id&gt;"
      data-instructions="אנחנו סטודיו לקדרות. תעזור בשאלות על חוגים, שעות פתיחה ומשלוחים."
      async
    ></script>
    מאפייןסוגברירת מחדלמשמעות
    data-keystringחובההמפתח שלכם שניתן לפרסום, ugp_…
    data-modelstringחובהאחד מהמודלים שהמפתח מפרט.
    data-titlestring“Ask a question”הכותרת של החלונית.
    data-greetingstring—ההודעה הראשונה שמבקרים רואים.
    data-instructionsstring—על מה האתר שלכם, במה לעזור, ובאיזה טון. נשלח עם כל שאלה; מבקרים יכולים לקרוא אותו בדף שלכם.
    data-labelstring“Ask”הטקסט של הלחצן.
    data-positionstringrightright או left.
    data-themestringautoauto (המכשיר של המבקר), light או dark.
    data-openbooleanfalsetrue פותח את החלונית כשהדף נטען.

    למה מפתח שניתן לפרסום יכול להופיע בדף ציבורי

    • הוא עובד רק לצ׳אט, ורק מתוך מסגרת העוזר של Undergrove עצמו. ה־API דוחה אותו בכל מקום אחר.
    • הוא עובד רק כשהמסגרת מוטמעת באחד מהאתרים שציינתם. הדפדפן מדווח איזה דף מטמיע את המסגרת, והשרת בודק זאת.
    • תמיד יש לו תקרת הוצאה יומית ורשימת מודלים. תוכנה שרצה מחוץ לדפדפן יכולה להתחזות לכל אתר, אז התייחסו למפתח כציבורי וקבעו את התקרה לסכום שאתם מוכנים להוציא ביום.
    • לכל מבקר יש מגבלת שאלות לדקה ומגבלה יומית, שמפורסמות ב־/api/status. המבקרים נספרים לפי גיבוב חד־כיווני עם מפתח של כתובת ה־IP שלהם, אף פעם לא לפי הכתובת עצמה, והספירות פגות תוך יומיים.

    התשובות מנצלות את המכסה והקרדיט של החשבון שלכם, כמו כל מפתח. במודלים שרצים במובלעת, המסגרת מצפינה כל הודעה בדפדפן של המבקר אל המובלעת המאומתת, כמו שסביבת העבודה עושה; למודלים אחרים מגיעים דרך השער, שקורא בקשות בזיכרון ולא שומר עותק. בשני המקרים האתר שלכם לא רואה את ההודעות.

    התג “Verified by Undergrove”

    אתר עם עוזר יכול להציג תג קטן שמקשר אל הדף של Undergrove עבור המפתח שלו. הדף הזה קורא את האתרים והמודלים של המפתח מהשרת של Undergrove, מאמת את המובלעת שמאחוריהם בדפדפן של המבקר, ומסביר מה התג טוען ומה לא. הדביקו את הקוד במקום שבו התג צריך להופיע.

    או, בלי סקריפט, תמונה וקישור (badge-dark.svg לדפים כהים):

    badge.htmlHTML
    <script src="https://undergrove.vercel.app/badge.js" data-key="ugp_…" async></script>
    • התג הוא קישור. הוא לא שולח שום בקשה מהדף שלכם ולא בודק שום דבר בעצמו; הדף שהוא פותח הוא שעושה את הבדיקה.
    • הוא מציין את המפתח שלכם שניתן לפרסום, שכבר נמצא בדף שלכם בשביל העוזר. דף התג מציג את האתרים והמודלים של המפתח, ואף פעם לא את החשבון, את התקרה או את מה שהוצא.
    • הקישור מבקש מהדפדפן לשלוח את המקור (origin) של האתר שלכם, כדי שהדף יוכל לומר למבקרים אם הגיעו מאחד האתרים של המפתח. תג שהועתק לאתר אחר מציין זאת שם.
    • הוא ערב לעוזר בלבד, לא לשאר האתר שלכם.

    אינטגרציות

    רוב הלקוחות של Undergrove עצמו בודקים את האטסטציה של המובלעת עם המאמת של Tinfoil, ואז מצפינים כל בקשה אל המובלעת הזו, כך שהממסר מעביר אותה הלאה בלי יכולת לקרוא אותה. כל אחד מהם מקבל את מפתח ה־API שלכם ומשתמש במזהי המודלים של Undergrove. הם עדיין לא נמצאים במאגרי חבילות; כל אחד נבנה מ־packages/ במאגר של האתר.

    הלקוחות ל־Rust, ל־Java ול־Kotlin והאפליקציה ל־Make משתמשים במקום זאת ב־API למפתחים, כי אין עבורם SDK של Tinfoil לבנות עליו: השער קורא כל בקשה בזיכרון, מאמת את המובלעת בעצמו ומדווח מה הוא אימת בכותרות של התשובה.

    חבילהעבורמה היא עושה
    @undergrove/sdkNodeלקוח בסגנון OpenAI עם הזרמה, כלים, embeddings, תמלול ודיבור.
    undergrovePythonלקוח ה־Python, עם LangChain (ChatUndergrove) ו־LlamaIndex (UndergroveLLM) כתוספות.
    GoGoלקוח שמבוסס על המאמת של Tinfoil ל־Go, עם הזרמה וקריאות לכלים.
    UndergroveSwiftחבילת Swift ל־macOS 14 ול־iOS 17, שמבוססת על המאמת של Tinfoil ל־Swift.
    @undergrove/ai-sdk-providerVercel AI SDKgenerateText, streamText וכלים, עם מה שאומת במטא־נתוני הספק של כל תשובה.
    @undergrove/langchainLangChain.jsמודל צ׳אט ו־embeddings.
    @undergrove/reactReactרכיבי צ׳אט ו־hooks, ונתיב שרת שמרחיק את מפתח ה־API מהדפדפן.
    create-undergrove-appפתיחnpm create undergrove-app: אפליקציית צ׳אט פרטית עם פריסה ל־Vercel בשתי פקודות.
    @undergrove/agentsסוכניםלולאת קריאה לכלים שמתעדת איזו מובלעת מאומתת ענתה על כל שלב, ברשומה שכל אחד יכול לבדוק אם נערכה, עם הצמדת מובלעות.
    undergroveRustלקוח ל־API למפתחים, עם הזרמה וקריאות לכלים, וראיות האטסטציה של השער לכל תשובה. הוא לא מצפין בקשות אל המובלעת בעצמו.
    dev.undergrove:undergroveJava ו־Kotlinלקוח ל־API למפתחים, עם הזרמה, כלים ו־embeddings, ועם coroutines ו־Flow ל־Kotlin. לא מצב מוצפן: השער קורא בקשות בזיכרון.
    undergrove[jupyter]Jupyterפקודת הקסם %%undergrove לתאים, עם משתני המחברת ו־DataFrames כהקשר, ווידג׳ט אטסטציה.
    @undergrove/officeExcel ו־Word=UNDERGROVE.ASK() בכל תא, וב־Word: סיכום, שכתוב או שאלה על הטקסט שנבחר.
    @undergrove/officeOutlookסיכום של ההודעה הפתוחה, וטיוטת תשובה שבודקים לפני השליחה.
    undergrove-obsidianObsidianשאלות על הפתק הפתוח, סיכום, שכתוב והמשך כתיבה, עם המובלעת המאומתת מתחת לכל תשובה. למחשב שולחני בלבד.
    Undergrove for AlfredAlfredמקלידים ug ושאלה: התשובה ב־Large Type ובלוח, מוצפנת דרך ה־CLI של undergrove.
    n8n-nodes-undergroven8nצומת קהילתי: שאלה למודל, יצירת embeddings והצגת רשימת המודלים, עם מה שאומת בכל פריט.
    Undergrove for ZapierZapierהפעולות Ask a Model ו־Create Embeddings, וכל בקשה מוצפנת אל המובלעת מתוך התהליך של ה־Zap.
    Undergrove for MakeMakeאפליקציה מותאמת אישית על ה־API למפתחים: השער מאמת את המובלעת ומדווח מה הוא אימת בפלט של כל מודול.
    Undergrove Enclave PinGitHub Actionsשלב שמאמת את המובלעת על ה־runner עם המאמת של Tinfoil ומכשיל את ה־job כשהיא לא זו שהצמדתם.

    עם 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);

    עם רכיבי React ותבנית הפתיחה, ההודעות עוברות מהדף לשרת שלכם, שאוטם אותן; מי שמפעיל את השרת הזה יכול לקרוא אותן, כמו בכל אינטגרציה בצד השרת.

    בדיקות ב־CI

    התחליף שהחבילות נבדקות מולו עונה למפתחות sandbox: כל מפתח שמתחיל ב־ugk_sandbox_. הבדיקות שלכם יכולות להשתמש במפתח כזה בלי סוד ובלי חיובים, ו־Undergrove עצמו דוחה אותם עם sandbox_key. התשובות שלו כתובות מראש, לא של מודל. ב־GitHub Actions, שלב אחד מפעיל אותו ומגדיר את UNDERGROVE_BASE_URL ואת 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

    מה הלאה

    זמין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.
    הבאמסלול המודל הראשון, התחברות לאלפא הפרטית, מגבלות שימוש משותפות.
    בנוי, עדיין לא מופעלמפתחות בשירות עצמי, תוכניות בתשלום וקרדיט משולם מראש לשימוש מעבר למכסה היומית.
    מתוכנןגישה לצוותים.
    דורש אימותהערכת אבטחה עצמאית של סביבת העבודה, הממסר והשער.