A developer of an AI app asked how to work with magpie and whether an app can register itself after one confirmation. The page says what exists: the gateway (find it via /api/hello and settings.json's port, the OpenAI, Anthropic, Responses and Gemini endpoints, /v1/models' fields), how a key magpie-<id> or the User-Agent's first word names the app in Usage, a row on the Agents page by an adapter PR in internal/agent (config-edit, Cindy-style own import link confirmed in the app, or a local API) with what the PR needs, what plugins can and can't do, and what isn't there: no link or API registers an app or installs a plugin, and magpie://import adds providers only. English and Chinese pages, linked from every docs page's nav and footer. TestThirdPartyAppIsCountedAsItself pins the attribution the page promises. go vet and the gateway tests pass; site node tests pass; pages checked in headless Chromium (no errors, TOC, no horizontal scroll at 390px).
178 lines
17 KiB
HTML
178 lines
17 KiB
HTML
<!doctype html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
<title>Integrate your app · magpie docs</title>
|
||
<meta name="description" content="How an AI app, agent or client works with magpie: the local gateway, its endpoints and models, being counted as your app, a row on the Agents page, and plugins.">
|
||
<meta name="color-scheme" content="light dark">
|
||
<meta name="theme-color" content="#fafafa">
|
||
<script>
|
||
(() => {
|
||
let mode = "system";
|
||
try { mode = localStorage.getItem("theme") || "system"; } catch {}
|
||
const q = matchMedia("(prefers-color-scheme: dark)");
|
||
const apply = () => {
|
||
const t = mode === "system" ? (q.matches ? "dark" : "light") : mode;
|
||
document.documentElement.dataset.theme = t;
|
||
document.querySelector('meta[name="theme-color"]').content = t === "dark" ? "#0b0b0c" : "#fafafa";
|
||
document.querySelectorAll("button.theme").forEach((b) => { b.dataset.mode = mode; b.title = "Theme: " + mode; });
|
||
};
|
||
q.addEventListener("change", () => mode === "system" && apply());
|
||
window.setTheme = (m) => { mode = m; try { m === "system" ? localStorage.removeItem("theme") : localStorage.setItem("theme", m); } catch {} apply(); };
|
||
window.themeMode = () => mode;
|
||
apply();
|
||
})();
|
||
</script>
|
||
<script>try{if(/Linux/.test(navigator.userAgent)&&!/Android/.test(navigator.userAgent)||localStorage.getItem("omarchy")||/[?&]omarchy=/.test(location.search))document.write('<script src="/omarchy.js"><\/script>')}catch(e){}</script>
|
||
<link rel="icon" href="/favicon.png" type="image/png">
|
||
<link rel="apple-touch-icon" href="/img/icon-256.png">
|
||
<link rel="alternate" hreflang="en" href="https://usemagpie.ai/docs/integrate">
|
||
<link rel="alternate" hreflang="zh" href="https://usemagpie.ai/docs/zh/integrate">
|
||
<link rel="stylesheet" href="/docs.css">
|
||
<meta property="og:title" content="Integrate your app">
|
||
<meta property="og:description" content="Use magpie's gateway from your app, or get a row on its Agents page.">
|
||
<meta property="og:image" content="https://usemagpie.ai/img/og.png">
|
||
<meta property="og:url" content="https://usemagpie.ai/docs/integrate">
|
||
<meta name="twitter:card" content="summary_large_image">
|
||
</head>
|
||
<body>
|
||
|
||
<nav><div class="wrap">
|
||
<a class="brand" href="/"><img class="bird" src="/img/magpie-small.svg" alt="">magpie <small>Docs</small></a>
|
||
<div class="links">
|
||
<a href="/">Home</a>
|
||
<a href="/docs/start">Get started</a>
|
||
<a href="/docs/intent">Intent routing</a>
|
||
<a href="/docs/plugins">Plugins</a>
|
||
<a href="/docs/import">Add to magpie</a>
|
||
<a href="/docs/integrate">Integrate</a>
|
||
<a href="https://github.com/yetone/magpie-releases/releases">Releases</a>
|
||
</div>
|
||
<div class="langs">
|
||
<button class="icon-btn lang" type="button" aria-label="Language" title="Language" aria-haspopup="true" aria-expanded="false" aria-controls="langs"><svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.4" aria-hidden="true"><circle cx="8" cy="8" r="6.25"/><path d="M1.75 8h12.5M8 1.75c1.7 1.75 2.55 3.83 2.55 6.25S9.7 12.5 8 14.25M8 1.75C6.3 3.5 5.45 5.58 5.45 8S6.3 12.5 8 14.25"/></svg></button>
|
||
<div class="lang-menu" id="langs" hidden>
|
||
<a href="/docs/integrate" hreflang="en" lang="en">English</a>
|
||
<a href="/docs/zh/integrate" hreflang="zh" lang="zh">中文</a>
|
||
</div>
|
||
</div>
|
||
<a class="icon-btn" href="https://discord.gg/vGSnD3ZKQF" aria-label="magpie on Discord" title="Join us on Discord"><svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M20.317 4.3698a19.7913 19.7913 0 00-4.8851-1.5152.0741.0741 0 00-.0785.0371c-.211.3753-.4447.8648-.6083 1.2495-1.8447-.2762-3.68-.2762-5.4868 0-.1636-.3933-.4058-.8742-.6177-1.2495a.077.077 0 00-.0785-.037 19.7363 19.7363 0 00-4.8852 1.515.0699.0699 0 00-.0321.0277C.5334 9.0458-.319 13.5799.0992 18.0578a.0824.0824 0 00.0312.0561c2.0528 1.5076 4.0413 2.4228 5.9929 3.0294a.0777.0777 0 00.0842-.0276c.4616-.6304.8731-1.2952 1.226-1.9942a.076.076 0 00-.0416-.1057c-.6528-.2476-1.2743-.5495-1.8722-.8923a.077.077 0 01-.0076-.1277c.1258-.0943.2517-.1923.3718-.2914a.0743.0743 0 01.0776-.0105c3.9278 1.7933 8.18 1.7933 12.0614 0a.0739.0739 0 01.0785.0095c.1202.099.246.1981.3728.2924a.077.077 0 01-.0066.1276 12.2986 12.2986 0 01-1.873.8914.0766.0766 0 00-.0407.1067c.3604.698.7719 1.3628 1.225 1.9932a.076.076 0 00.0842.0286c1.961-.6067 3.9495-1.5219 6.0023-3.0294a.077.077 0 00.0313-.0552c.5004-5.177-.8382-9.6739-3.5485-13.6604a.061.061 0 00-.0312-.0286zM8.02 15.3312c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9555-2.4189 2.157-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.9555 2.4189-2.1569 2.4189zm7.9748 0c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9554-2.4189 2.1569-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.946 2.4189-2.1568 2.4189Z"/></svg></a>
|
||
<a class="icon-btn" href="https://github.com/yetone/magpie" aria-label="magpie on GitHub" title="Source on GitHub"><svg viewBox="0 0 16 16" fill="currentColor" aria-hidden="true"><path d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0 0 16 8c0-4.42-3.58-8-8-8Z"/></svg></a>
|
||
<button class="icon-btn theme" type="button" aria-label="Theme" title="Theme: system"><svg class="i-system" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="1.75" y="2.75" width="12.5" height="8.5" rx="1.5"/><path d="M5.5 14h5M8 11.25V14"/></svg><svg class="i-light" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><circle cx="8" cy="8" r="3"/><path d="M8 1v1.5M8 13.5V15M1 8h1.5M13.5 8H15M3 3l1 1M12 12l1 1M3 13l1-1M12 4l1-1"/></svg><svg class="i-dark" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round"><path d="M13.5 9.5A5.5 5.5 0 0 1 6.5 2.5a5.5 5.5 0 1 0 7 7Z"/></svg></button>
|
||
<a class="btn small" href="/#get">Download</a>
|
||
</div></nav>
|
||
|
||
<main class="wrap doc">
|
||
<aside class="toc">
|
||
<p>Integrate an app</p>
|
||
<a href="#overview">Overview</a>
|
||
<a href="#gateway">Talk to the gateway</a>
|
||
<a href="#agent">A row on the Agents page</a>
|
||
<a href="#plugins">Plugins</a>
|
||
<a href="#not">What isn’t there</a>
|
||
</aside>
|
||
|
||
<article>
|
||
<p class="eyebrow" id="overview">Docs · For developers</p>
|
||
<h1>Integrate your app</h1>
|
||
<p class="lede">For developers of an AI app, agent or client who want it to work well with magpie. There are three levels, from no work on magpie’s side to a row of your own on the Agents page.</p>
|
||
|
||
<div class="table-wrap"><table>
|
||
<tr><th>You want</th><th>Route</th><th>Change to magpie</th></tr>
|
||
<tr><td>Your app’s requests served by magpie, counted under its own name in Usage</td><td><a href="#gateway">Talk to the gateway</a></td><td>none</td></tr>
|
||
<tr><td>A row on the Agents page: detected, its model picked there, put back on disconnect</td><td><a href="#agent">An agent adapter</a></td><td>a pull request</td></tr>
|
||
<tr><td>A new model source (a subscription, a vendor), or rewriting requests</td><td><a href="#plugins">A plugin</a></td><td>none</td></tr>
|
||
</table></div>
|
||
|
||
<h2 id="gateway"><a class="anchor" href="#gateway">1. Talk to the gateway</a></h2>
|
||
<p>magpie runs a local LLM gateway. Every model the user has in magpie (subscriptions, API keys, local models, routing groups) is served there under one id each, so your app needs one provider entry, not one per vendor.</p>
|
||
|
||
<h3>Find it</h3>
|
||
<p>The gateway listens on <code>127.0.0.1:3425</code>, or on the port in <code>port</code> of magpie’s <code>settings.json</code> (<code>~/.config/magpie/settings.json</code>, the same path under the user’s folder on Windows; <code>$XDG_CONFIG_HOME/magpie</code> when that is set). <code>GET /api/hello</code> answers <code>{"name":"magpie","version":"…"}</code> when it is magpie. Read that file, never write it. magpie must be running (the app or <code>magpie serve</code>); an app that finds nothing there offers its own providers as usual.</p>
|
||
<div class="code">
|
||
<span class="lang">Shell</span>
|
||
<button class="copy" data-copy="ex-hello">Copy</button>
|
||
<pre id="ex-hello">curl -s http://127.0.0.1:3425/api/hello
|
||
curl -s http://127.0.0.1:3425/v1/models -H "Authorization: Bearer magpie-acme"</pre>
|
||
</div>
|
||
|
||
<h3>Endpoints</h3>
|
||
<p>Requests go straight through when the vendor speaks your API, and are translated otherwise, streaming, tool calls, images and reasoning included. The base URL is <code>http://127.0.0.1:<port>/v1</code> for an OpenAI client and <code>http://127.0.0.1:<port></code> for an Anthropic or Gemini one.</p>
|
||
<div class="table-wrap"><table>
|
||
<tr><th>Path</th><th>API</th></tr>
|
||
<tr><td><code>/v1/chat/completions</code></td><td>OpenAI Chat Completions</td></tr>
|
||
<tr><td><code>/v1/responses</code></td><td>OpenAI Responses</td></tr>
|
||
<tr><td><code>/v1/messages</code>, <code>/v1/messages/count_tokens</code></td><td>Anthropic Messages</td></tr>
|
||
<tr><td><code>/v1beta/models/{model}:generateContent</code> (and <code>:streamGenerateContent</code>)</td><td>Google Gemini</td></tr>
|
||
<tr><td><code>/v1/models</code></td><td>the catalog</td></tr>
|
||
</table></div>
|
||
|
||
<h3>The key names your app</h3>
|
||
<p>On this machine the gateway takes any key. Send <code>magpie-<your-app-id></code> (<code>Authorization: Bearer magpie-acme</code>, or <code>x-api-key</code>) and every call is counted as <code>acme</code> in Usage, the Requests log and the routing rules that match on agents. Without it, magpie counts the call by the first word of your <code>User-Agent</code> (<code>Acme/1.4 (darwin)</code> is <code>Acme</code>); an SDK’s own User-Agent (<code>OpenAI/JS …</code>, <code>ai-sdk/…</code>) would count it as that SDK. From another computer (<span class="ui">Settings</span> → <span class="ui">Share on local network</span>) the key must be one of the user’s gateway keys, and the User-Agent names your app.</p>
|
||
|
||
<h3>Models</h3>
|
||
<p><code>GET /v1/models</code> lists what the user has turned on: <code>id</code> (<code>provider/model</code>, or a routing group’s name, sent as it is), <code>display_name</code>, <code>owned_by</code> (the provider), <code>reasoning</code> and <code>supported_reasoning_levels</code> (<code>[{"effort":"low"},…]</code>), <code>context_window</code> and <code>max_output_tokens</code> when known, and <code>modalities</code> when magpie knows whether it takes images. List them from there rather than shipping a list: the user adds and removes providers in magpie. Reasoning is asked in your API’s own field (<code>reasoning_effort</code>, <code>reasoning.effort</code>, Anthropic’s <code>thinking</code> or <code>output_config.effort</code>). <code>?format=text</code> gives the ids one a line.</p>
|
||
|
||
<h3>Optional</h3>
|
||
<p>Send <code>X-Magpie-Session: <id></code> with a conversation’s requests and <code>GET /v1/magpie/route?session=<id></code> says which model a routing group picked for the turn and the fallbacks it tried, before the first token (long-poll with <code>after=<seq>&wait=<s></code>). <code>GET /v1/magpie/quotas</code> lists the user’s subscription allowances. Both answer only this machine, or another with a gateway key. The <a href="https://github.com/yetone/magpie/blob/main/docs/reference.md#connecting-anything-else">reference</a> has the details.</p>
|
||
|
||
<div class="callout"><b>Keep the user’s providers.</b> Add magpie as one more provider (named Magpie) beside the ones the user set up in your app, rather than replacing them, so turning magpie off is picking another provider.</div>
|
||
|
||
<h2 id="agent"><a class="anchor" href="#agent">2. A row on the Agents page</a></h2>
|
||
<p>The agents magpie lists (Claude Code, Codex, OpenCode, dsh, Alma, Cindy…) are adapters in <a href="https://github.com/yetone/magpie/tree/main/internal/agent"><code>internal/agent</code></a>, one file each. A row gets your app’s name and icon, shows when your app is installed, lets the user pick its model (and reasoning) from magpie’s catalog, follows the catalog as providers change, and on disconnect puts back what your app had before. There is no runtime registration: a row is added by a pull request to magpie, and ships in the next release.</p>
|
||
<p>An adapter is an <a href="https://github.com/yetone/magpie/blob/main/internal/agent/agent.go"><code>Agent</code></a> value. What your app offers decides its shape:</p>
|
||
<ul>
|
||
<li><b>magpie edits your config file</b> (most adapters; <a href="https://github.com/yetone/magpie/blob/main/internal/agent/fx.go"><code>fx.go</code></a>, <a href="https://github.com/yetone/magpie/blob/main/internal/agent/empryo.go"><code>empryo.go</code></a>). <code>Dir</code>, <code>Path</code> and <code>Bin</code> find the app; each <code>Field</code> (<code>model</code>, <code>effort</code>…) has <code>Get</code>, <code>Set</code> and <code>Options</code>. Connecting adds a <code>magpie</code> provider at the gateway, keyed <code>magpie-<id></code>, and sets your model to one of magpie’s; the agent’s default or <code>Unwire</code> takes magpie’s entries out and restores what was there. Only the keys magpie sets are touched. <code>Notice</code> says when your app must be restarted; <code>Sync</code> rewrites a model list your app keeps in a file.</li>
|
||
<li><b>Your app takes a provider only through its own import link, confirmed in your app</b> (<a href="https://github.com/yetone/magpie/blob/main/internal/agent/cindy.go"><code>cindy.go</code></a>). <code>Import</code> returns the link (<code>yourapp://provider/import?…</code> with the gateway’s URL, the key <code>magpie-<id></code> and the endpoints), the row’s button opens it, and the user says yes once, in your app. <code>Added</code> reads, read-only, whether your app has magpie already. magpie writes nothing of yours. This suits an app that keeps its providers in a database or encrypted.</li>
|
||
<li><b>Your app has a local API while it runs</b> (Alma): magpie adds itself as a provider and sets the default model through it.</li>
|
||
</ul>
|
||
<p>A pull request needs the adapter and its entry in <code>agents.go</code>, <code>UA</code> (what your User-Agent begins with, lower-case) so Usage knows your calls, an icon in <code>internal/gui/assets/icons</code>, tests that run under a temporary <code>HOME</code> (connect, switch, disconnect restoring the original file), and a row in the reference’s Agents table. What helps most is a stable, documented config format or import link, and where your app keeps it on each system.</p>
|
||
|
||
<h2 id="plugins"><a class="anchor" href="#plugins">3. Plugins</a></h2>
|
||
<p>A plugin is an npm package magpie runs; the user installs it from <span class="ui">Settings</span> → <span class="ui">Plugins</span> or with <code>magpie plugin add <package></code>. <b>Provider plugins</b> (OpenCode’s <code>auth</code> hook, or a pi package) add a source of models, such as a subscription magpie doesn’t sign in to itself. <b>Gateway middleware</b> rewrites requests and replies as they pass. See the <a href="/docs/plugins">plugin guide</a>. A plugin can’t add a row to the Agents page or write another app’s config, so it is not how an app registers itself.</p>
|
||
|
||
<h2 id="not"><a class="anchor" href="#not">What isn’t there</a></h2>
|
||
<ul>
|
||
<li>No API or link lets another app register itself as an agent, or install a plugin, without the user: plugins are installed in magpie by the user, and agent rows come from adapters in magpie’s source.</li>
|
||
<li><code>magpie://import</code> links add a <em>provider</em> (a source of models, with its key), not an agent or an app: see <a href="/docs/import">Add to magpie</a>.</li>
|
||
</ul>
|
||
|
||
<p style="margin-top:48px">Questions, or an adapter you’d like to discuss first: open an <a href="https://github.com/yetone/magpie/issues">issue</a> or ask on <a href="https://discord.gg/vGSnD3ZKQF">Discord</a>.</p>
|
||
</article>
|
||
</main>
|
||
|
||
<footer><div class="wrap">
|
||
<span>© 2026 magpie · MIT</span>
|
||
<div class="links">
|
||
<a href="/">Home</a>
|
||
<a href="/docs/intent">Intent routing</a>
|
||
<a href="/docs/plugins">Plugins</a>
|
||
<a href="/docs/import">Add to magpie</a>
|
||
<a href="/docs/integrate">Integrate</a>
|
||
<a href="https://github.com/yetone/magpie">GitHub</a>
|
||
<a href="https://discord.gg/vGSnD3ZKQF">Discord</a>
|
||
<a href="https://github.com/yetone/magpie-releases/releases">Releases</a>
|
||
</div>
|
||
</div></footer>
|
||
|
||
<script>
|
||
(() => {
|
||
const next = { system: "light", light: "dark", dark: "system" };
|
||
document.querySelectorAll("button.theme").forEach((b) => {
|
||
b.dataset.mode = themeMode();
|
||
b.title = "Theme: " + themeMode();
|
||
b.addEventListener("click", () => setTheme(next[themeMode()]));
|
||
});
|
||
|
||
document.querySelectorAll(".copy").forEach((b) => b.addEventListener("click", async () => {
|
||
try {
|
||
await navigator.clipboard.writeText(document.getElementById(b.dataset.copy).textContent);
|
||
b.textContent = "Copied"; setTimeout(() => (b.textContent = "Copy"), 1400);
|
||
} catch {}
|
||
}));
|
||
})();
|
||
</script>
|
||
<script src="/docs.js"></script>
|
||
</body>
|
||
</html>
|