<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: HiveGate</title>
    <description>The latest articles on DEV Community by HiveGate (@hivegate-).</description>
    <link>https://dev.to/hivegate-</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4187344%2Ff04796c1-5a92-4c81-8828-3c9bfef9d5aa.png</url>
      <title>DEV Community: HiveGate</title>
      <link>https://dev.to/hivegate-</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/hivegate-"/>
    <language>en</language>
    <item>
      <title>Deploying Agno agents to production as a REST API</title>
      <dc:creator>HiveGate</dc:creator>
      <pubDate>Tue, 20 Oct 2026 16:00:02 +0000</pubDate>
      <link>https://dev.to/hivegate-/deploying-agno-agents-to-production-as-a-rest-api-1a1d</link>
      <guid>https://dev.to/hivegate-/deploying-agno-agents-to-production-as-a-rest-api-1a1d</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://hivegate.dev/blog/deploy-agno-agents-production-rest-api/" rel="noopener noreferrer"&gt;hivegate.dev&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;An &lt;a href="https://github.com/agno-agi/agno" rel="noopener noreferrer"&gt;Agno&lt;/a&gt; agent that works in a script is a few lines of Python. Turning it into something a web or mobile app can call for thousands of users takes more: an HTTP endpoint, a conversation per user, history that survives restarts, API keys, model keys kept on the server, and a container you can deploy.&lt;/p&gt;

&lt;p&gt;This post shows how &lt;a href="https://github.com/hivegate-ai/hivegate" rel="noopener noreferrer"&gt;HiveGate&lt;/a&gt;, an MIT-licensed FastAPI service built on Agno, handles each of those. It also covers when you should skip HiveGate and use Agno's own AgentOS.&lt;/p&gt;

&lt;h2&gt;
  
  
  First: you may not need HiveGate
&lt;/h2&gt;

&lt;p&gt;Agno ships &lt;a href="https://docs.agno.com/agent-os/introduction" rel="noopener noreferrer"&gt;AgentOS&lt;/a&gt;, which serves agents defined in code over HTTP, with sessions, human approval, RBAC and tracing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;agno.agent&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Agent&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;agno.db.postgres&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;PostgresDb&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;agno.models.google&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Gemini&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;agno.os&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AgentOS&lt;/span&gt;

&lt;span class="n"&gt;support_agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;support-agent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;Gemini&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gemini-3-flash-preview&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;PostgresDb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;db_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;postgresql+psycopg://ai:ai@localhost:5432/ai&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;add_history_to_context&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AgentOS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agents&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;support_agent&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;get_app&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your agents live in your codebase and that covers what you need, use AgentOS. It's maintained by the Agno team and tracks Agno releases.&lt;/p&gt;

&lt;p&gt;HiveGate is for a different case: agents that act &lt;em&gt;for your users&lt;/em&gt; in their own Google or Microsoft accounts, and agents, prompts and skills that you manage as data through an API instead of shipping code for each change.&lt;/p&gt;

&lt;h2&gt;
  
  
  Agents are records, not code
&lt;/h2&gt;

&lt;p&gt;In HiveGate you create an agent with a POST. The &lt;code&gt;template&lt;/code&gt; becomes the agent's system prompt, stored as a versioned prompt in Postgres. The &lt;code&gt;config&lt;/code&gt; block maps onto Agno's &lt;code&gt;Agent&lt;/code&gt; options.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:8000/v2/agents &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-API-Key: &lt;/span&gt;&lt;span class="nv"&gt;$HIVEGATE_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "id": "support-agent",
    "name": "support-agent",
    "template": "You are the support assistant for Acme. Answer briefly and cite the help center when you can.",
    "description": "Customer support for Acme",
    "tags": ["available_tools:email,calendar"],
    "config": {
      "enable_memory": true,
      "enable_history": true,
      "num_history_runs": 3
    }
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;available_tools:&lt;/code&gt; tag switches on HiveGate's built-in toolkits for this agent. Leave it out and the agent gets none of them. Agents that need other tools can point at MCP servers over Streamable HTTP through &lt;code&gt;config.worker_config.mcp_servers&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Changing the prompt later is a &lt;code&gt;PUT /v2/prompts/support_agent&lt;/code&gt; (the prompt ID is the agent's name in snake case). The next chat picks up the new template, so a prompt fix doesn't need a redeploy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Chatting, with sessions per user
&lt;/h2&gt;

&lt;p&gt;Every chat request names a &lt;code&gt;user_id&lt;/code&gt; and a &lt;code&gt;session_id&lt;/code&gt;. Both are required, as are &lt;code&gt;timezone&lt;/code&gt; and &lt;code&gt;locale&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:8000/v2/agents/support-agent/chat &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-API-Key: &lt;/span&gt;&lt;span class="nv"&gt;$HIVEGATE_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "message": "My invoice from last month looks wrong.",
    "user_id": "user-1842",
    "session_id": "ticket-5521",
    "timezone": "America/New_York",
    "locale": "en-US",
    "stream": false
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The non-streaming response is a JSON object with &lt;code&gt;content&lt;/code&gt;, &lt;code&gt;session_id&lt;/code&gt;, &lt;code&gt;run_id&lt;/code&gt;, &lt;code&gt;status&lt;/code&gt;, the &lt;code&gt;tools&lt;/code&gt; the agent called, and &lt;code&gt;token_usage&lt;/code&gt; for that call.&lt;/p&gt;

&lt;p&gt;Here is what happens to those two IDs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;History.&lt;/strong&gt; Agno stores each run in a Postgres session table, one table per agent. With &lt;code&gt;enable_history&lt;/code&gt; on, the last &lt;code&gt;num_history_runs&lt;/code&gt; runs of the same session go back into the model's context. A follow-up with the same &lt;code&gt;session_id&lt;/code&gt; continues the conversation, even after a restart or on another replica.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Memory.&lt;/strong&gt; With &lt;code&gt;enable_memory&lt;/code&gt; on, Agno's memory manager keeps facts about the user across sessions, keyed by &lt;code&gt;user_id&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agent cache.&lt;/strong&gt; HiveGate builds the Agno &lt;code&gt;Agent&lt;/code&gt; once per (prompt, agent, model, user, session) and caches it in the process, so later turns skip setup.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Per-request context goes into Agno's &lt;code&gt;session_state&lt;/code&gt;. HiveGate fills it from the request: the user's name and profile (from &lt;code&gt;user_profile&lt;/code&gt;), &lt;code&gt;timezone&lt;/code&gt; and &lt;code&gt;locale&lt;/code&gt;. If you send a &lt;code&gt;tenant_profile&lt;/code&gt;, knowledge search is filtered to that tenant's documents, so one deployment can serve several customers without mixing their data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Streaming
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;stream&lt;/code&gt; defaults to &lt;code&gt;true&lt;/code&gt;. The response is Server-Sent Events, one &lt;code&gt;event: message&lt;/code&gt; per Agno event, each with a JSON &lt;code&gt;data&lt;/code&gt; line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt;

&lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;message&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Summarize my open tickets.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user-1842&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;session_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ticket-5521&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;timezone&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;UTC&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;locale&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;en&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;POST&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://localhost:8000/v2/agents/support-agent/chat&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;X-API-Key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;HIVEGATE_KEY&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;120&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;iter_lines&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;continue&lt;/span&gt;
        &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):])&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;event&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RunContent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;flush&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If a model call fails mid-stream, you get an &lt;code&gt;event: error&lt;/code&gt; with a generic message. The details stay in the server log, so provider internals don't leak to clients.&lt;/p&gt;

&lt;h2&gt;
  
  
  Picking the model per request
&lt;/h2&gt;

&lt;p&gt;A request can set &lt;code&gt;model&lt;/code&gt;. Otherwise HiveGate uses &lt;code&gt;DEFAULT_CHAT_MODEL&lt;/code&gt;, and falls back to &lt;code&gt;gemini-3-flash-preview&lt;/code&gt; if that's unset. Besides pinned IDs such as &lt;code&gt;claude-sonnet-4-6&lt;/code&gt;, it accepts aliases like &lt;code&gt;anthropic:sonnet-latest&lt;/code&gt; or &lt;code&gt;google:flash-latest&lt;/code&gt;. An alias resolves to the vendor's newest model in that tier and never moves you up to a pricier tier. Model keys stay in the server's environment; clients never see them.&lt;/p&gt;

&lt;h2&gt;
  
  
  API keys and cost per user
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;/v2/*&lt;/code&gt; requires an &lt;code&gt;X-API-Key&lt;/code&gt;. Create keys with the admin secret:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:8000/admin/api-keys &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-Admin-Secret: &lt;/span&gt;&lt;span class="nv"&gt;$ADMIN_SECRET&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"name": "acme-web", "rate_limit": 1000}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The raw key is in the &lt;code&gt;api_key&lt;/code&gt; field of the response, and it's shown only once. For local work, &lt;code&gt;AUTH_DISABLED=true&lt;/code&gt; skips the check. Never set it on a deployment other people can reach.&lt;/p&gt;

&lt;p&gt;Every model call is metered: tokens, cache reads, tool calls, duration and USD cost go into a &lt;code&gt;token_usage&lt;/code&gt; table, including failed and abandoned runs. To see which users cost the most:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-API-Key: &lt;/span&gt;&lt;span class="nv"&gt;$USAGE_KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"http://localhost:8000/usage/summary?window=7d&amp;amp;group_by=user"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The usage endpoints accept a key with the &lt;code&gt;usage:read&lt;/code&gt; scope. A key whose only scope is &lt;code&gt;usage:read&lt;/code&gt; is refused on &lt;code&gt;/v2&lt;/code&gt;, so a dashboard key can't call agents.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running it
&lt;/h2&gt;

&lt;p&gt;Locally, Docker Compose starts Postgres and Qdrant and seeds demo agents:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/hivegate-ai/hivegate &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;hivegate
&lt;span class="nb"&gt;cp&lt;/span&gt; .env.example .env    &lt;span class="c"&gt;# set GOOGLE_API_KEY, uncomment AUTH_DISABLED=true&lt;/span&gt;
docker compose up &lt;span class="nt"&gt;-d&lt;/span&gt;
./scripts/dev_setup.sh &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;source&lt;/span&gt; .venv/bin/activate &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; ./scripts/start_server.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a server, use the prebuilt multi-arch image &lt;code&gt;ghcr.io/hivegate-ai/hivegate:latest&lt;/code&gt;. The &lt;a href="https://hivegate.dev/deploy/" rel="noopener noreferrer"&gt;deploy page&lt;/a&gt; has one-click buttons for Render, Railway and Koyeb, plus Kubernetes and AWS, Azure and GCP manifests. The settings that matter:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Variable&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;DB_HOST&lt;/code&gt;, &lt;code&gt;DB_PORT&lt;/code&gt;, &lt;code&gt;DB_USER&lt;/code&gt;, &lt;code&gt;DB_PASS&lt;/code&gt;, &lt;code&gt;DB_DATABASE&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Postgres for agents, sessions, tokens and usage&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ADMIN_SECRET&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Protects &lt;code&gt;/admin/*&lt;/code&gt;, where API keys are created&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;GOOGLE_API_KEY&lt;/code&gt; / &lt;code&gt;ANTHROPIC_API_KEY&lt;/code&gt; / &lt;code&gt;OPENAI_API_KEY&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;At least one model provider&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;SECRET_TOKEN_ENC_KEY&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Fernet key that encrypts stored user OAuth tokens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;QDRANT_URL&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Optional, for per-tenant knowledge&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;GET /health&lt;/code&gt; is the health check. The deploy page ends with a five-step smoke test: health, list agents, create an agent, one real LLM call, then cleanup.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to know before scaling out
&lt;/h2&gt;

&lt;p&gt;Conversation history is in Postgres, so any replica can serve the next turn. Two things live in process memory: the cached &lt;code&gt;Agent&lt;/code&gt; objects, and runs that are paused waiting for a human to approve a tool call. A paused run has to be resumed on the process that paused it. If you use approvals, run one replica or route each session to the same instance. The &lt;a href="https://hivegate.dev/blog/human-approval-agent-tool-calls/" rel="noopener noreferrer"&gt;post on approvals&lt;/a&gt; covers that flow.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to use which
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Agents defined in code, one team, standard Agno features: &lt;strong&gt;AgentOS&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Agents that act in each user's Gmail, Outlook or calendar; agents and prompts edited through an API; per-tenant knowledge; supervisor teams with container workers: &lt;strong&gt;HiveGate&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Deploy in one click from the &lt;a href="https://hivegate.dev/deploy/" rel="noopener noreferrer"&gt;deploy page&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Read the source, open an issue or star the repo on &lt;a href="https://github.com/hivegate-ai/hivegate" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If this is useful, a star helps other people find the project: &lt;a href="https://github.com/hivegate-ai/hivegate" rel="noopener noreferrer"&gt;⭐ github.com/hivegate-ai/hivegate&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>python</category>
      <category>ai</category>
      <category>opensource</category>
      <category>fastapi</category>
    </item>
  </channel>
</rss>
