<?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: Air Pipe</title>
    <description>The latest articles on DEV Community by Air Pipe (airpipe).</description>
    <link>https://dev.to/airpipe</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%2Forganization%2Fprofile_image%2F9645%2Ff3d9f8c4-479d-4304-b1c0-d570d070e701.png</url>
      <title>DEV Community: Air Pipe</title>
      <link>https://dev.to/airpipe</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/airpipe"/>
    <language>en</language>
    <item>
      <title>How to build your own MCP server</title>
      <dc:creator>Kav Pather</dc:creator>
      <pubDate>Tue, 04 Aug 2026 17:05:00 +0000</pubDate>
      <link>https://dev.to/airpipe/how-to-build-your-own-mcp-server-11gb</link>
      <guid>https://dev.to/airpipe/how-to-build-your-own-mcp-server-11gb</guid>
      <description>&lt;p&gt;Most MCP tutorials hand you a Node project. You install an SDK, write a tool&lt;br&gt;
handler, wire up stdio, and end up with something that runs on your laptop as&lt;br&gt;
you, with your credentials, for exactly one user.&lt;/p&gt;

&lt;p&gt;That's fine for a demo. It's not something you can give a customer.&lt;/p&gt;

&lt;p&gt;Here's the other way, end to end: a database, one config file, a token, and a&lt;br&gt;
URL you paste into Claude. Every step below is a real command against a real&lt;br&gt;
pack — nothing elided, nothing left as an exercise.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Time:&lt;/strong&gt; about 15 minutes. &lt;strong&gt;You'll need:&lt;/strong&gt; an &lt;a href="https://airpipe.io" rel="noopener noreferrer"&gt;Air Pipe&lt;/a&gt; account (free tier is&lt;br&gt;
enough), a Postgres database, and an MCP client — Claude Desktop, Claude Code,&lt;br&gt;
Cursor, anything that speaks MCP.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 1 — Get a Postgres database
&lt;/h2&gt;

&lt;p&gt;If you already have one, skip ahead. If not, any of these work and all have a&lt;br&gt;
usable free tier:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Provider&lt;/th&gt;
&lt;th&gt;What you get&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://neon.tech" rel="noopener noreferrer"&gt;Neon&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Serverless Postgres, free tier, connection string in the dashboard&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://supabase.com" rel="noopener noreferrer"&gt;Supabase&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Postgres + a UI to browse rows while you test&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Local&lt;/td&gt;
&lt;td&gt;&lt;code&gt;docker run -e POSTGRES_PASSWORD=pw -p 5432:5432 postgres:16&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;What you need out of it is one connection string:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;postgresql://user:password@host:5432/dbname
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A local Postgres works for following along, but your managed Air Pipe instance&lt;br&gt;
can't reach &lt;code&gt;localhost&lt;/code&gt; — so if you want the tools live from Claude Desktop, use&lt;br&gt;
a hosted database or self-host the Air Pipe binary next to your local one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;On SSL:&lt;/strong&gt; most hosted providers require it. If your first query fails with&lt;br&gt;
&lt;code&gt;SSL is required&lt;/code&gt;, append &lt;code&gt;?sslmode=require&lt;/code&gt; to the connection string. Neon&lt;br&gt;
needs this; Supabase includes it in the string it gives you.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 2 — Create the schema
&lt;/h2&gt;

&lt;p&gt;Three tables. Only one of them is your data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="n"&gt;EXTENSION&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="n"&gt;pgcrypto&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- A tenant is one of YOUR customers. Ignore it entirely while it's just you;&lt;/span&gt;
&lt;span class="c1"&gt;-- it's what makes step 8 possible without a rewrite.&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="n"&gt;mcp_tenants&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt;         &lt;span class="n"&gt;UUID&lt;/span&gt;        &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;gen_random_uuid&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="n"&gt;name&lt;/span&gt;       &lt;span class="nb"&gt;TEXT&lt;/span&gt;        &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="n"&gt;TIMESTAMPTZ&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- Issued token metadata — the revocation denylist. The token string itself is&lt;/span&gt;
&lt;span class="c1"&gt;-- never stored, only its jti claim.&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="n"&gt;mcp_tokens&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;jti&lt;/span&gt;        &lt;span class="n"&gt;UUID&lt;/span&gt;        &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;tenant_id&lt;/span&gt;  &lt;span class="n"&gt;UUID&lt;/span&gt;        &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;mcp_tenants&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;DELETE&lt;/span&gt; &lt;span class="k"&gt;CASCADE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;subject&lt;/span&gt;    &lt;span class="nb"&gt;TEXT&lt;/span&gt;        &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;name&lt;/span&gt;       &lt;span class="nb"&gt;TEXT&lt;/span&gt;        &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="s1"&gt;'default'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="n"&gt;TIMESTAMPTZ&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="n"&gt;expires_at&lt;/span&gt; &lt;span class="n"&gt;TIMESTAMPTZ&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;revoked_at&lt;/span&gt; &lt;span class="n"&gt;TIMESTAMPTZ&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- The resource your tools read and write. Swap this for your own table.&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="n"&gt;mcp_tasks&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="n"&gt;id&lt;/span&gt;         &lt;span class="n"&gt;UUID&lt;/span&gt;        &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;gen_random_uuid&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="n"&gt;tenant_id&lt;/span&gt;  &lt;span class="n"&gt;UUID&lt;/span&gt;        &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;mcp_tenants&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;DELETE&lt;/span&gt; &lt;span class="k"&gt;CASCADE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;title&lt;/span&gt;      &lt;span class="nb"&gt;TEXT&lt;/span&gt;        &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="n"&gt;status&lt;/span&gt;     &lt;span class="nb"&gt;TEXT&lt;/span&gt;        &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="s1"&gt;'open'&lt;/span&gt; &lt;span class="k"&gt;CHECK&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'open'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'done'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
  &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="n"&gt;TIMESTAMPTZ&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
  &lt;span class="n"&gt;updated_at&lt;/span&gt; &lt;span class="n"&gt;TIMESTAMPTZ&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;NOW&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="n"&gt;idx_mcp_tasks_tenant&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;mcp_tasks&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tenant_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;created_at&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;psql &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$DATABASE_URL&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; schema.sql
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;pgcrypto&lt;/code&gt; is only needed for &lt;code&gt;gen_random_uuid()&lt;/code&gt; on Postgres 12 and earlier —&lt;br&gt;
it's built in from 13 on, and the &lt;code&gt;IF NOT EXISTS&lt;/code&gt; makes the line harmless either&lt;br&gt;
way.&lt;/p&gt;

&lt;p&gt;Seed a tenant and a couple of rows so there's something to see:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;mcp_tenants&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'11111111-1111-1111-1111-111111111111'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Acme Inc'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;mcp_tasks&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tenant_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'11111111-1111-1111-1111-111111111111'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Ship the MCP launch post'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'open'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'11111111-1111-1111-1111-111111111111'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Review Q3 numbers'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;        &lt;span class="s1"&gt;'done'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 3 — Set two variables
&lt;/h2&gt;

&lt;p&gt;In the Air Pipe dashboard, under your environment's managed variables (or as&lt;br&gt;
&lt;code&gt;ap_var&lt;/code&gt;s if you're self-hosting):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;DATABASE_URL&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;the connection string from step 1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;SOLO_SECRET&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;a 32+ character random string&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Generate the secret rather than typing one — it's the only thing standing&lt;br&gt;
between the internet and your database:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;openssl rand &lt;span class="nt"&gt;-base64&lt;/span&gt; 48
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both are referenced as &lt;code&gt;a|ap_var::NAME|&lt;/code&gt; in the config, so they never appear in&lt;br&gt;
the file you commit.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 4 — Write the config
&lt;/h2&gt;

&lt;p&gt;Here's the whole thing. One file, two tools.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;McpTasks&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;MCP tools over Postgres, guarded by a single shared HS256 token.&lt;/span&gt;

&lt;span class="c1"&gt;# Who this server says it is when a client calls initialize (engine &amp;gt;= 1.38.0).&lt;/span&gt;
&lt;span class="na"&gt;mcp_servers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;tasks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Tasks&lt;/span&gt;
    &lt;span class="na"&gt;instructions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;-&lt;/span&gt;
      &lt;span class="s"&gt;A task list backed by Postgres. Use list_tasks to read tasks (optionally&lt;/span&gt;
      &lt;span class="s"&gt;filtered to "open" or "done") and create_task to add one. Both tools&lt;/span&gt;
      &lt;span class="s"&gt;require the bearer token issued by the operator.&lt;/span&gt;
    &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;

&lt;span class="na"&gt;global&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;databases&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;main&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;postgres&lt;/span&gt;
      &lt;span class="na"&gt;conn_string&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a|ap_var::DATABASE_URL|"&lt;/span&gt;

&lt;span class="na"&gt;interfaces&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

  &lt;span class="c1"&gt;# MCP tool: list_tasks   ·   HTTP: POST /solo/tasks&lt;/span&gt;
  &lt;span class="na"&gt;solo/tasks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;POST&lt;/span&gt;
    &lt;span class="na"&gt;summary&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;List all tasks&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;List every task, newest first. Optionally filter by status.&lt;/span&gt;
    &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;tasks&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;mcp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="na"&gt;tool_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;list_tasks&lt;/span&gt;
      &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;List all tasks. Optional status filter ("open" or "done").&lt;/span&gt;

    &lt;span class="na"&gt;actions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ValidateToken&lt;/span&gt;
        &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;a|headers|&lt;/span&gt;
        &lt;span class="na"&gt;hide_data_on_success&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
        &lt;span class="na"&gt;assert&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;http_code_on_error&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;401&lt;/span&gt;
          &lt;span class="na"&gt;error_message&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Invalid&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;or&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;missing&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;token"&lt;/span&gt;
          &lt;span class="na"&gt;tests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;airpipe-jwt&lt;/span&gt;
              &lt;span class="na"&gt;is_not_null&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
              &lt;span class="na"&gt;is_valid_jwt&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;a|ap_var::SOLO_SECRET|&lt;/span&gt;
        &lt;span class="na"&gt;post_transforms&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;extract_value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;jwt_claims&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;CheckBody&lt;/span&gt;
        &lt;span class="na"&gt;run_when_succeeded&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;actions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;ValidateToken&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
          &lt;span class="na"&gt;http_code_on_error&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;400&lt;/span&gt;
        &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;a|body|&lt;/span&gt;
        &lt;span class="na"&gt;hide_data_on_success&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
        &lt;span class="na"&gt;assert&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;tests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;status&lt;/span&gt;
              &lt;span class="na"&gt;is_not_null&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
              &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Optional status filter — "open" or "done".&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ListTasks&lt;/span&gt;
        &lt;span class="na"&gt;run_when_succeeded&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;CheckBody&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
        &lt;span class="na"&gt;database&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;
        &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;SELECT id, title, status, created_at&lt;/span&gt;
          &lt;span class="s"&gt;FROM mcp_tasks&lt;/span&gt;
          &lt;span class="s"&gt;WHERE ($1::text IS NULL OR status = $1::text)&lt;/span&gt;
          &lt;span class="s"&gt;ORDER BY created_at DESC&lt;/span&gt;
          &lt;span class="s"&gt;LIMIT 200;&lt;/span&gt;
        &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;a|body::status-&amp;gt;default(null)|&lt;/span&gt;

  &lt;span class="c1"&gt;# MCP tool: create_task   ·   HTTP: POST /solo/tasks/create&lt;/span&gt;
  &lt;span class="na"&gt;solo/tasks/create&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;POST&lt;/span&gt;
    &lt;span class="na"&gt;summary&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Create a task&lt;/span&gt;
    &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;tasks&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;mcp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="na"&gt;tool_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;create_task&lt;/span&gt;
      &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Create a new task. Requires a title; status defaults to "open".&lt;/span&gt;

    &lt;span class="na"&gt;actions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ValidateToken&lt;/span&gt;
        &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;a|headers|&lt;/span&gt;
        &lt;span class="na"&gt;hide_data_on_success&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
        &lt;span class="na"&gt;assert&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;http_code_on_error&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;401&lt;/span&gt;
          &lt;span class="na"&gt;error_message&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Invalid&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;or&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;missing&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;token"&lt;/span&gt;
          &lt;span class="na"&gt;tests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;airpipe-jwt&lt;/span&gt;
              &lt;span class="na"&gt;is_not_null&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
              &lt;span class="na"&gt;is_valid_jwt&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;a|ap_var::SOLO_SECRET|&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;CheckBody&lt;/span&gt;
        &lt;span class="na"&gt;run_when_succeeded&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;actions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;ValidateToken&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
          &lt;span class="na"&gt;http_code_on_error&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;400&lt;/span&gt;
        &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;a|body|&lt;/span&gt;
        &lt;span class="na"&gt;hide_data_on_success&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
        &lt;span class="na"&gt;assert&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;http_code_on_error&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;400&lt;/span&gt;
          &lt;span class="na"&gt;error_message&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;title&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;is&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;required"&lt;/span&gt;
          &lt;span class="na"&gt;tests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;title&lt;/span&gt;
              &lt;span class="na"&gt;is_not_null&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
              &lt;span class="na"&gt;is_not_empty&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
              &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;The task title.&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;status&lt;/span&gt;
              &lt;span class="na"&gt;is_not_null&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
              &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Optional status — "open" (default) or "done".&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;CreateTask&lt;/span&gt;
        &lt;span class="na"&gt;run_when_succeeded&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;CheckBody&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
        &lt;span class="na"&gt;database&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;
        &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;INSERT INTO mcp_tasks (tenant_id, title, status)&lt;/span&gt;
          &lt;span class="s"&gt;VALUES ($1::uuid, $2, COALESCE($3, 'open'))&lt;/span&gt;
          &lt;span class="s"&gt;RETURNING id, title, status, created_at;&lt;/span&gt;
        &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;11111111-1111-1111-1111-111111111111"&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;a|CheckBody::title|&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;a|body::status-&amp;gt;default(null)|&lt;/span&gt;
        &lt;span class="na"&gt;post_transforms&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;extract_value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;[0]"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Five things worth pointing at:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;mcp_servers&lt;/code&gt; is the server; &lt;code&gt;mcp:&lt;/code&gt; blocks are the tools.&lt;/strong&gt; The declaration at&lt;br&gt;
the top is what a client and a registry see before any tool runs — more on it&lt;br&gt;
after step 8. Delete it and everything still works, just anonymously.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The &lt;code&gt;mcp:&lt;/code&gt; block is the only thing that makes it a tool.&lt;/strong&gt; Delete it and you&lt;br&gt;
have an ordinary HTTP route. Keep it and you have both — same auth, same query,&lt;br&gt;
same trace, one definition.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Auth is not MCP-specific.&lt;/strong&gt; Air Pipe takes the client's&lt;br&gt;
&lt;code&gt;Authorization: Bearer&lt;/code&gt; token, forwards it into the interface as the&lt;br&gt;
&lt;code&gt;airpipe-jwt&lt;/code&gt; header, and runs the same actions an HTTP request would.&lt;br&gt;
&lt;strong&gt;Securing an MCP tool is exactly securing a route.&lt;/strong&gt; One model to learn, not&lt;br&gt;
two.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;CheckBody&lt;/code&gt; is what the AI sees.&lt;/strong&gt; The MCP &lt;code&gt;inputSchema&lt;/code&gt; is generated from&lt;br&gt;
those assert tests — which is why each carries a &lt;code&gt;description:&lt;/code&gt;. Write them for&lt;br&gt;
a reader who isn't you, because the model picks tools by reading them.&lt;br&gt;
&lt;code&gt;is_not_null: false&lt;/code&gt; is an always-pass predicate: it declares the field as&lt;br&gt;
optional without requiring it. And because only &lt;code&gt;CheckBody&lt;/code&gt; reads &lt;code&gt;a|body|&lt;/code&gt;,&lt;br&gt;
the token never leaks into the tool's schema.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Parameters are bound, not interpolated.&lt;/strong&gt; &lt;code&gt;$1&lt;/code&gt;, &lt;code&gt;$2&lt;/code&gt; with a &lt;code&gt;params:&lt;/code&gt; list —&lt;br&gt;
so a task titled &lt;code&gt;'); DROP TABLE mcp_tasks; --&lt;/code&gt; is a task title.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 5 — Deploy
&lt;/h2&gt;

&lt;p&gt;Nothing to build and nothing to host.&lt;/p&gt;

&lt;p&gt;On managed Air Pipe, paste the file into the dashboard editor and hit deploy —&lt;br&gt;
that validates it on the way in. If you're using the Air Pipe MCP tools from&lt;br&gt;
your own AI client, "validate and deploy this config" does the same from the&lt;br&gt;
chat, and installing the pack (below) does it without either.&lt;/p&gt;

&lt;p&gt;Self-hosting is one command — point the binary at the directory holding the&lt;br&gt;
file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;airpipe server &lt;span class="nt"&gt;--config-dir&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;--api-key&lt;/span&gt; &amp;lt;your-key&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It serves on port 4111 by default, so the URLs in the next steps are&lt;br&gt;
&lt;code&gt;http://localhost:4111/…&lt;/code&gt;. Run &lt;code&gt;airpipe login&lt;/code&gt; once and you can drop&lt;br&gt;
&lt;code&gt;--api-key&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Step 6 — Mint a token
&lt;/h2&gt;

&lt;p&gt;Once, at &lt;a href="https://jwt.io" rel="noopener noreferrer"&gt;jwt.io&lt;/a&gt;: algorithm &lt;strong&gt;HS256&lt;/strong&gt;, secret = your&lt;br&gt;
&lt;code&gt;SOLO_SECRET&lt;/code&gt;, payload:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"sub"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"me"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"exp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1798761600&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Copy the token. Rotating &lt;code&gt;SOLO_SECRET&lt;/code&gt; invalidates it.&lt;/p&gt;

&lt;p&gt;Prefer the command line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 - &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;PY&lt;/span&gt;&lt;span class="sh"&gt;'
import base64, hmac, hashlib, json, os
def b64(b): return base64.urlsafe_b64encode(b).rstrip(b'=')
secret = os.environ['SOLO_SECRET'].encode()
msg = b64(json.dumps({"alg":"HS256","typ":"JWT"}).encode()) + b'.' + &lt;/span&gt;&lt;span class="se"&gt;\&lt;/span&gt;&lt;span class="sh"&gt;
      b64(json.dumps({"sub":"me","exp":1798761600}).encode())
sig = b64(hmac.new(secret, msg, hashlib.sha256).digest())
print((msg + b'.' + sig).decode())
&lt;/span&gt;&lt;span class="no"&gt;PY
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 7 — Verify it before you touch the client
&lt;/h2&gt;

&lt;p&gt;Debugging through an MCP client is miserable — a failure shows up as "the tool&lt;br&gt;
didn't work." Check with curl first. MCP is JSON-RPC over HTTP, so you can&lt;br&gt;
drive it directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;BASE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://your-airpipe-host/&amp;lt;org&amp;gt;/&amp;lt;&lt;span class="nb"&gt;env&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;   &lt;span class="c"&gt;# self-hosted: no /&amp;lt;org&amp;gt;/&amp;lt;env&amp;gt;&lt;/span&gt;
&lt;span class="nv"&gt;TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&amp;lt;the token from step 6&amp;gt;

&lt;span class="c"&gt;# List the tools&lt;/span&gt;
curl &lt;span class="nt"&gt;-sX&lt;/span&gt; POST &lt;span class="nv"&gt;$BASE&lt;/span&gt;/mcp &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$TOKEN&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="s1"&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;'{"jsonrpc":"2.0","id":1,"method":"tools/list"}'&lt;/span&gt; | jq &lt;span class="s1"&gt;'.result.tools[].name'&lt;/span&gt;
&lt;span class="c"&gt;# → "list_tasks"&lt;/span&gt;
&lt;span class="c"&gt;# → "create_task"&lt;/span&gt;

&lt;span class="c"&gt;# Call one&lt;/span&gt;
curl &lt;span class="nt"&gt;-sX&lt;/span&gt; POST &lt;span class="nv"&gt;$BASE&lt;/span&gt;/mcp &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$TOKEN&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="s1"&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;'{"jsonrpc":"2.0","id":2,"method":"tools/call",
       "params":{"name":"create_task","arguments":{"title":"Draft the changelog"}}}'&lt;/span&gt;

&lt;span class="c"&gt;# The same tool over plain HTTP — note the header name changes&lt;/span&gt;
curl &lt;span class="nt"&gt;-sX&lt;/span&gt; POST &lt;span class="nv"&gt;$BASE&lt;/span&gt;/solo/tasks &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"airpipe-jwt: &lt;/span&gt;&lt;span class="nv"&gt;$TOKEN&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="s1"&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;'{"status":"open"}'&lt;/span&gt; | jq &lt;span class="s1"&gt;'.data.ListTasks.data'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;tools/list&lt;/code&gt; returns your two tools and &lt;code&gt;tools/call&lt;/code&gt; returns a row, you're&lt;br&gt;
done — everything after this is client configuration.&lt;/p&gt;

&lt;p&gt;Two failures worth naming, because they're the common ones:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;401 &lt;code&gt;Invalid or missing token&lt;/code&gt;&lt;/strong&gt; — the secret used to sign doesn't match
&lt;code&gt;SOLO_SECRET&lt;/code&gt;, or &lt;code&gt;exp&lt;/code&gt; is in the past. Decode the token at jwt.io and check
the expiry first; it's usually that.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A database error on the query action&lt;/strong&gt; — the connection string can't be
reached from your Air Pipe instance. &lt;code&gt;localhost&lt;/code&gt; is the usual culprit, SSL
the other.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Step 8 — Point Claude at it
&lt;/h2&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"my-tasks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://your-airpipe-host/&amp;lt;org&amp;gt;/&amp;lt;env&amp;gt;/mcp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"headers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"Authorization"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Bearer &amp;lt;your-token&amp;gt;"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Claude Desktop keeps this at&lt;br&gt;
&lt;code&gt;~/Library/Application Support/Claude/claude_desktop_config.json&lt;/code&gt; on macOS and&lt;br&gt;
&lt;code&gt;%APPDATA%\Claude\claude_desktop_config.json&lt;/code&gt; on Windows. Claude Code:&lt;br&gt;
&lt;code&gt;claude mcp add --transport http my-tasks https://your-airpipe-host/&amp;lt;org&amp;gt;/&amp;lt;env&amp;gt;/mcp --header "Authorization: Bearer &amp;lt;token&amp;gt;"&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Restart the client. Ask &lt;em&gt;"what's on my task list?"&lt;/em&gt; and it queries your&lt;br&gt;
database.&lt;/p&gt;

&lt;p&gt;You also have, from that same file and with no extra work: an HTTP endpoint for&lt;br&gt;
the clients that don't speak MCP, OpenAPI docs, Prometheus metrics, and an&lt;br&gt;
OpenTelemetry trace for every tool call showing which action ran and how long&lt;br&gt;
the query took. That last one matters more than it sounds — when a model calls&lt;br&gt;
a tool and gets a confusing answer, the trace is how you find out whether the&lt;br&gt;
tool was wrong or the model was.&lt;/p&gt;
&lt;h2&gt;
  
  
  Give the server a name, not just tools
&lt;/h2&gt;

&lt;p&gt;Every client calls &lt;code&gt;initialize&lt;/code&gt; before it lists anything, and that response is&lt;br&gt;
where the server says who it is. Skip it and yours introduces itself with a&lt;br&gt;
built-in name and no description — a listing that's a bare label above a wall of&lt;br&gt;
tool descriptions. That's what &lt;code&gt;mcp_servers&lt;/code&gt; at the top of the config fixes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;mcp_servers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;tasks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Tasks&lt;/span&gt;               &lt;span class="c1"&gt;# -&amp;gt; serverInfo.title, the name in the client's UI&lt;/span&gt;
    &lt;span class="na"&gt;instructions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;&amp;gt;-&lt;/span&gt;           &lt;span class="c1"&gt;# -&amp;gt; the initialize result's `instructions`&lt;/span&gt;
      &lt;span class="s"&gt;A task list backed by Postgres. Use list_tasks to read tasks and&lt;/span&gt;
      &lt;span class="s"&gt;create_task to add one. Both require the operator's bearer token.&lt;/span&gt;
    &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;              &lt;span class="c1"&gt;# adopt every tool that names no server&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;instructions&lt;/code&gt; matters more than it looks. MCP registries — mcp.so, Glama,&lt;br&gt;
Smithery, PulseMCP — read a remote server's listing description straight off&lt;br&gt;
that field. There is no other place to write one, so an unlisted description&lt;br&gt;
isn't a blank field somewhere; it's a listing nobody clicks.&lt;/p&gt;

&lt;p&gt;Check it the same way you checked the tools:&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;-sX&lt;/span&gt; POST &lt;span class="nv"&gt;$BASE&lt;/span&gt;/mcp &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&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;'{"jsonrpc":"2.0","id":1,"method":"initialize",
       "params":{"protocolVersion":"2025-06-18","capabilities":{},
                 "clientInfo":{"name":"curl","version":"1"}}}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  | jq &lt;span class="s1"&gt;'{title: .result.serverInfo.title, instructions: .result.instructions}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Declaring a server and contributing tools to it are separate on purpose.&lt;/strong&gt; An&lt;br&gt;
MCP server is a named group of tools, not a property of one config: tools in&lt;br&gt;
&lt;em&gt;any&lt;/em&gt; of your configs join a server by id, so one identity can cover tools spread&lt;br&gt;
across many files — which also means the declaration can live alone in its own&lt;br&gt;
config (&lt;code&gt;interfaces: {}&lt;/code&gt;) and survive whichever tool file you rename next.&lt;/p&gt;

&lt;p&gt;The id is a route segment, so a second declaration is a second endpoint from the&lt;br&gt;
same deployment — a public server and an internal one, say:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;mcp_servers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;tasks&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;                       &lt;span class="c1"&gt;# served at /mcp&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Tasks&lt;/span&gt;
    &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;tasks-admin&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;                 &lt;span class="c1"&gt;# served at /mcp/tasks-admin&lt;/span&gt;
    &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Tasks (admin)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;    &lt;span class="na"&gt;mcp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="na"&gt;tool_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;purge_tasks&lt;/span&gt;
      &lt;span class="na"&gt;server&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tasks-admin&lt;/span&gt;      &lt;span class="c1"&gt;# published only on the named endpoint&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ids are 1–64 characters of &lt;code&gt;a-z&lt;/code&gt;, &lt;code&gt;0-9&lt;/code&gt; or &lt;code&gt;-&lt;/code&gt;, only one server may be the&lt;br&gt;
default, and a tool naming a server nothing declares publishes on &lt;em&gt;no&lt;/em&gt; server&lt;br&gt;
rather than the wrong one. Needs engine ≥ 1.38.0.&lt;/p&gt;
&lt;h2&gt;
  
  
  The hole most MCP servers have
&lt;/h2&gt;

&lt;p&gt;Worth knowing regardless of what you build with: &lt;strong&gt;&lt;code&gt;tools/call&lt;/code&gt; runs your code,&lt;br&gt;
&lt;code&gt;tools/list&lt;/code&gt; doesn't.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Listing tools returns metadata — names, descriptions, input schemas. Whatever&lt;br&gt;
auth you put inside your handlers never fires for discovery. So a server with&lt;br&gt;
locked-down calls can still let anyone who knows the URL enumerate every tool&lt;br&gt;
you expose and its full schema. They can't call anything. They can read the map.&lt;/p&gt;

&lt;p&gt;For a personal server, fine. For an endpoint you offer customers, that catalog&lt;br&gt;
is often the sensitive part — your tool names are a description of your product.&lt;/p&gt;

&lt;p&gt;Close it by adding one line per tool, pointing at an interface that re-runs the&lt;br&gt;
token check when a client lists tools:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;    &lt;span class="na"&gt;mcp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="na"&gt;tool_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;list_tasks&lt;/span&gt;
      &lt;span class="na"&gt;list_authorizer&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;authorize-discovery&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the gate itself — an ordinary interface, not a tool:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;  &lt;span class="na"&gt;authorize-discovery&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;POST&lt;/span&gt;
    &lt;span class="na"&gt;summary&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Authorize MCP tool discovery for the caller's token.&lt;/span&gt;
    &lt;span class="na"&gt;tags&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;internal&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

    &lt;span class="na"&gt;actions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ValidateToken&lt;/span&gt;
        &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;a|headers|&lt;/span&gt;
        &lt;span class="na"&gt;hide_data_on_success&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
        &lt;span class="na"&gt;assert&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;http_code_on_error&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;401&lt;/span&gt;
          &lt;span class="na"&gt;error_message&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Invalid&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;or&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;missing&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;token"&lt;/span&gt;
          &lt;span class="na"&gt;tests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;airpipe-jwt&lt;/span&gt;
              &lt;span class="na"&gt;is_not_null&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
              &lt;span class="na"&gt;is_valid_jwt&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;a|ap_var::SOLO_SECRET|&lt;/span&gt;
        &lt;span class="na"&gt;response_on_success&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;http_code&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;200&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now an unauthenticated &lt;code&gt;tools/list&lt;/code&gt; returns &lt;code&gt;{"result":{"tools":[]}}&lt;/code&gt; — not even&lt;br&gt;
the names.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;response_on_success: { http_code: 200 }&lt;/code&gt; is required.&lt;/strong&gt; The gate is&lt;br&gt;
fail-closed on anything that isn't an explicit 2xx, and an interface whose&lt;br&gt;
actions all succeed leaves the status code unset — which reads as "not&lt;br&gt;
authorized" and hides every gated tool &lt;em&gt;even for a valid token&lt;/em&gt;. If your tools&lt;br&gt;
vanish after adding the gate, this is why.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Needs engine ≥ 1.7.0. Drop the &lt;code&gt;list_authorizer:&lt;/code&gt; line to make discovery public.&lt;/p&gt;
&lt;h2&gt;
  
  
  Now the part that matters: your customers
&lt;/h2&gt;

&lt;p&gt;Everything above is one token, one grant — everyone who holds it sees every row.&lt;br&gt;
Right for pointing an AI at your own database. Useless the moment you have&lt;br&gt;
users.&lt;/p&gt;

&lt;p&gt;The multi-tenant shape is the same config with the token doing more work. Your&lt;br&gt;
backend already knows who's logged in, so it mints a per-user token carrying a&lt;br&gt;
&lt;code&gt;tenant_id&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;&lt;span class="nv"&gt;TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-sX&lt;/span&gt; POST &lt;span class="nv"&gt;$BASE&lt;/span&gt;/auth/exchange &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"x-exchange-secret: &lt;/span&gt;&lt;span class="nv"&gt;$EXCHANGE_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="s1"&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;'{"tenant_id":"11111111-1111-1111-1111-111111111111",
       "subject":"user-123","name":"laptop"}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.data.Result.data.token'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then every query scopes to the claim in that token instead of a hardcoded id:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ListTasks&lt;/span&gt;
        &lt;span class="na"&gt;database&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;
        &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;SELECT id, title, status, created_at&lt;/span&gt;
          &lt;span class="s"&gt;FROM mcp_tasks&lt;/span&gt;
          &lt;span class="s"&gt;WHERE tenant_id = $1::uuid&lt;/span&gt;
            &lt;span class="s"&gt;AND ($2::text IS NULL OR status = $2::text)&lt;/span&gt;
          &lt;span class="s"&gt;ORDER BY created_at DESC&lt;/span&gt;
          &lt;span class="s"&gt;LIMIT 200;&lt;/span&gt;
        &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;a|ValidateJwt::tenant_id|&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;a|body::status-&amp;gt;default(null)|&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A row from another tenant doesn't match. Cross-tenant access is structurally&lt;br&gt;
impossible rather than merely forbidden — there's no code path where forgetting&lt;br&gt;
a &lt;code&gt;WHERE&lt;/code&gt; clause leaks a customer's data, because the filter &lt;em&gt;is&lt;/em&gt; the query.&lt;br&gt;
One endpoint, every customer, each seeing only their own rows.&lt;/p&gt;
&lt;h3&gt;
  
  
  Revocation, which stateless JWTs can't do alone
&lt;/h3&gt;

&lt;p&gt;A signature check can't tell a revoked token from a valid one — that's what the&lt;br&gt;
&lt;code&gt;mcp_tokens&lt;/code&gt; table is for. Every tool re-checks the token's &lt;code&gt;jti&lt;/code&gt; against it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;CheckTokenActive&lt;/span&gt;
        &lt;span class="na"&gt;run_when_succeeded&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;actions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;ValidateJwt&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
          &lt;span class="na"&gt;http_code_on_error&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;401&lt;/span&gt;
        &lt;span class="na"&gt;database&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;
        &lt;span class="na"&gt;hide_data_on_success&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
        &lt;span class="na"&gt;query&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;SELECT (&lt;/span&gt;
            &lt;span class="s"&gt;$1::uuid IS NULL OR EXISTS (&lt;/span&gt;
              &lt;span class="s"&gt;SELECT 1 FROM mcp_tokens&lt;/span&gt;
              &lt;span class="s"&gt;WHERE jti = $1::uuid AND revoked_at IS NULL AND expires_at &amp;gt; NOW()&lt;/span&gt;
            &lt;span class="s"&gt;)&lt;/span&gt;
          &lt;span class="s"&gt;) AS ok;&lt;/span&gt;
        &lt;span class="na"&gt;params&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;a|ValidateJwt::jti-&amp;gt;default(null)|&lt;/span&gt;
        &lt;span class="na"&gt;assert&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;http_code_on_error&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;401&lt;/span&gt;
          &lt;span class="na"&gt;error_message&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Token&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;revoked&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;or&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;expired"&lt;/span&gt;
          &lt;span class="na"&gt;tests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;[0]ok"&lt;/span&gt;
              &lt;span class="na"&gt;is_equal_to&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Revoking is a call, not an SSH session:&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;-sX&lt;/span&gt; POST &lt;span class="nv"&gt;$BASE&lt;/span&gt;/auth/revoke &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"x-exchange-secret: &lt;/span&gt;&lt;span class="nv"&gt;$EXCHANGE_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="s1"&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;'{"jti":"&amp;lt;the jti returned at mint time&amp;gt;"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The next call is refused: &lt;code&gt;401 Token revoked or expired&lt;/code&gt; on the HTTP route, and&lt;br&gt;
an error result from the tool over MCP. This is the piece a naive JWT setup&lt;br&gt;
forgets.&lt;/p&gt;
&lt;h3&gt;
  
  
  Already using Auth0, Clerk or Cognito?
&lt;/h3&gt;

&lt;p&gt;Skip the exchange hop entirely. Point &lt;code&gt;is_valid_jwt&lt;/code&gt; at your provider's JWKS and&lt;br&gt;
verify their RS256 tokens directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;airpipe-jwt&lt;/span&gt;
              &lt;span class="na"&gt;is_not_null&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
              &lt;span class="na"&gt;is_valid_jwt&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
                &lt;span class="na"&gt;jwks_url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;a|ap_var::OIDC_JWKS_URL|&lt;/span&gt;
                &lt;span class="na"&gt;alg&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;RS256&lt;/span&gt;
                &lt;span class="na"&gt;iss&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;a|ap_var::OIDC_ISSUER|&lt;/span&gt;
                &lt;span class="na"&gt;aud&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;a|ap_var::OIDC_AUDIENCE|&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Air Pipe fetches and caches the keys, selects the signer by the token's &lt;code&gt;kid&lt;/code&gt;,&lt;br&gt;
and enforces &lt;code&gt;iss&lt;/code&gt; / &lt;code&gt;aud&lt;/code&gt; / &lt;code&gt;exp&lt;/code&gt;. Provider key rotation just works. Add a&lt;br&gt;
&lt;code&gt;tenant_id&lt;/code&gt; claim in your IdP and the scoping above is unchanged. Needs engine&lt;br&gt;
≥ 0.196.0.&lt;/p&gt;

&lt;h2&gt;
  
  
  The shortcut
&lt;/h2&gt;

&lt;p&gt;Everything on this page ships as one pack, both tiers, tested end to end — the&lt;br&gt;
schema, the seed endpoint, the single-token tools, the tenant-scoped tools, the&lt;br&gt;
discovery gate, the token lifecycle routes, and the OIDC variant. Fork it, set&lt;br&gt;
two variables, deploy.&lt;/p&gt;

&lt;p&gt;If you only want steps 1 through 8 — one token, your own database, no tenancy —&lt;br&gt;
take &lt;strong&gt;MCP Quickstart&lt;/strong&gt; instead. It's the same idea stripped to two tools over&lt;br&gt;
one table, with discovery already gated. Start there and move up when you have&lt;br&gt;
customers; the config shape doesn't change.&lt;/p&gt;

&lt;p&gt;You can absolutely hand-roll all of this with the TypeScript SDK instead. You'll&lt;br&gt;
also be hand-rolling the auth, the tenant scoping, the discovery gate, the&lt;br&gt;
revocation denylist, the traces, and a parallel REST API for the clients that&lt;br&gt;
don't speak MCP. That's the trade.&lt;/p&gt;

&lt;h2&gt;
  
  
  Known limits, so you're not surprised
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tokens are long-lived bearers.&lt;/strong&gt; MCP clients today authenticate with a
static bearer pasted into config — there's no interactive OAuth flow yet. Keep
&lt;code&gt;exp&lt;/code&gt; short and rely on the denylist for revocation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One statement per action&lt;/strong&gt; on Postgres — the driver prepares the query, and
a prepared statement holds one command. Use &lt;code&gt;multi: true&lt;/code&gt; for a
multi-statement DDL block (engine ≥ 0.196.0).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;HTTP responses are wrapped&lt;/strong&gt; in a &lt;code&gt;{"data":{"&amp;lt;Action&amp;gt;":{"data": …}}}&lt;/code&gt; action
trace, which is why the curl examples pipe through &lt;code&gt;jq&lt;/code&gt;. MCP clients parse the
tool result for you.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Is &lt;code&gt;tools/list&lt;/code&gt; open on your MCP server right now? Worth checking.&lt;/p&gt;

&lt;h3&gt;
  
  
  Skip the setup
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://airpipe.io/marketplace/pack/mcp-postgres-starter" rel="noopener noreferrer"&gt;MCP Postgres Starter&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Turn your Postgres data into secure MCP tools any AI client (Claude Desktop, Claude Code, Cursor) can call. &lt;/p&gt;

&lt;h3&gt;
  
  
  Related packs
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://airpipe.io/marketplace/pack/mcp-quickstart" rel="noopener noreferrer"&gt;MCP Quickstart
&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The smallest useful MCP server: two tools over one Postgres table, guarded by a single shared token, in one config file. Point Claude Desktop, Claude Code, Cursor or any MCP client at your database with no SDK, no Node project and nothing to host. An Air Pipe interface is an HTTP route; add an mcp block and the same interface is also an MCP tool, secured by the same in-config token check. Tool discovery (tools/list) is gated by that same token via list_authorizer, so an unauthenticated client cannot even enumerate your tools or their input schemas. Includes a seed endpoint that creates the table and sample data in one curl.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>api</category>
      <category>mcp</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Latency based container scaling with Orbit</title>
      <dc:creator>Kav Pather</dc:creator>
      <pubDate>Mon, 17 Feb 2025 15:01:53 +0000</pubDate>
      <link>https://dev.to/airpipe/latency-based-container-scaling-with-orbit-1c3i</link>
      <guid>https://dev.to/airpipe/latency-based-container-scaling-with-orbit-1c3i</guid>
      <description>&lt;p&gt;Haven't read Part 1? Start with &lt;a href="https://dev.to/airpipe/building-orbit-a-lightweight-container-orchestration-tool-in-rust-1072"&gt;Building Orbit: A Lightweight Container Orchestrator in Rust&lt;/a&gt; to learn about our journey's beginning.&lt;/p&gt;

&lt;p&gt;In our previous article, we introduced Orbit, our lightweight container orchestrator built in Rust. Since then, we've made significant improvements driven by both community feedback and production requirements. Let's dive into the technical evolution that's making Orbit even more powerful and efficient.&lt;/p&gt;

&lt;h2&gt;
  
  
  Community-Driven Development
&lt;/h2&gt;

&lt;p&gt;One of the most exciting aspects of Orbit's development has been the community engagement. A perfect example is our implementation of &lt;a href="https://en.wikipedia.org/wiki/CoDel" rel="noopener noreferrer"&gt;CoDel (Controlled Delay)&lt;/a&gt; for scaling decisions, which came directly from a community member's suggestion on Medium. We're also grateful to community members like &lt;a href="https://github.com/josselinchevalay" rel="noopener noreferrer"&gt;Josselin Chevalay&lt;/a&gt; who contributed the &lt;code&gt;pull_policy&lt;/code&gt; feature in our latest release, allowing control over container image pulling behavior. This collaborative approach will continue to help shape Orbit's feature set and technical direction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Technical Evolution: Key Improvements
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. CoDel (Controlled Delay) - Inspired Scaling: Latency-Driven Container Orchestration
&lt;/h3&gt;

&lt;p&gt;Unlike traditional orchestrators that rely solely on CPU and memory metrics, we've implemented &lt;a href="https://en.wikipedia.org/wiki/CoDel" rel="noopener noreferrer"&gt;CoDel-based/inspired scaling&lt;/a&gt; - a feature not natively available in Kubernetes or other major orchestrators. Here's how it works:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;CoDelMetrics&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;service_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;sojourn_times&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;VecDeque&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Instant&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;first_above_time&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Instant&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;last_scale_time&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Instant&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;CoDelConfig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;adaptive-scaling&lt;/span&gt;
&lt;span class="na"&gt;instance_count&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;min&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
  &lt;span class="na"&gt;max&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;

&lt;span class="c1"&gt;# CoDel-inspired adaptive scaling based on request latency&lt;/span&gt;
&lt;span class="na"&gt;codel&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;target&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;100ms&lt;/span&gt;                   &lt;span class="c1"&gt;# Target latency threshold&lt;/span&gt;
  &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;1s&lt;/span&gt;                    &lt;span class="c1"&gt;# Interval for checking delays&lt;/span&gt;
  &lt;span class="na"&gt;consecutive_intervals&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3&lt;/span&gt;        &lt;span class="c1"&gt;# Number of intervals above target before scaling&lt;/span&gt;
  &lt;span class="na"&gt;max_scale_step&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;              &lt;span class="c1"&gt;# Maximum instances to scale up at once&lt;/span&gt;
  &lt;span class="na"&gt;scale_cooldown&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;30s&lt;/span&gt;            &lt;span class="c1"&gt;# Minimum time between scaling actions&lt;/span&gt;
  &lt;span class="na"&gt;overload_status_code&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;503&lt;/span&gt;      &lt;span class="c1"&gt;# Return 503 when overloaded&lt;/span&gt;

&lt;span class="c1"&gt;# Fine-tune scaling behavior&lt;/span&gt;
&lt;span class="na"&gt;scaling_policy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;cooldown_duration&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;60s&lt;/span&gt;         &lt;span class="c1"&gt;# Wait time between scaling actions&lt;/span&gt;
  &lt;span class="na"&gt;scale_down_threshold_percentage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;50.0&lt;/span&gt;  &lt;span class="c1"&gt;# Scale down if usage below 50%&lt;/span&gt;

&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;
      &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;airpipeio/infoapp:latest&lt;/span&gt;
      &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
          &lt;span class="na"&gt;node_port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4335&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The CoDel inspired implementation monitors request latency and makes intelligent scaling decisions based on both immediate and historical performance data. Benefits include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;More responsive scaling based on actual service performance&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Better handling of latency spikes&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Prevention of unnecessary scale-ups during temporary load increases&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt; that this is just our initial implementation, and we will continue to improve where possible and perhaps rename when appropriate.&lt;/p&gt;

&lt;p&gt;Key Differences from Traditional CoDel:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Service-Level Application&lt;/strong&gt; :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Our implementation applies CoDel principles at the service level rather than packet level&lt;/li&gt;
&lt;li&gt;Uses request latency instead of packet sojourn time&lt;/li&gt;
&lt;li&gt;Focuses on scaling rather than packet dropping&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;&lt;strong&gt;State Management&lt;/strong&gt; :&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;This is simpler than traditional CoDel's state machine.
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;CoDelMetrics&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;sojourn_times&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;VecDeque&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Instant&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;first_above_time&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Instant&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;last_scale_time&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Instant&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Health Monitoring
&lt;/h3&gt;

&lt;p&gt;We've added the usual health monitoring with TCP health checks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;HealthCheckConfig&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;startup_timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;startup_failure_threshold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;liveness_period&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;liveness_failure_threshold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;tcp_check&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;TcpHealthCheck&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This system provides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Configurable health check parameters&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;TCP-level connectivity verification&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Granular control over failure thresholds&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Separate startup and liveness checks&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Performance Optimizations
&lt;/h3&gt;

&lt;p&gt;We've made several low-level optimizations to improve performance:&lt;/p&gt;

&lt;h4&gt;
  
  
  Switching to FxHashMap/FxHashSet
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;rustc_hash&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="n"&gt;FxHashMap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;FxHashSet&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="n"&gt;INSTANCE_STORE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;OnceLock&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;
    &lt;span class="nb"&gt;Arc&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;RwLock&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;FxHashMap&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;FxHashMap&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Uuid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;InstanceMetadata&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;OnceLock&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By replacing standard HashMap with FxHashMap:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Reduced memory overhead&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Faster hash computation&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Better performance for string keys&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Lower collision rates in our specific use cases&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Improved Resource Management
&lt;/h3&gt;

&lt;p&gt;We've implemented a more sophisticated resource management system:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;ResourceThresholds&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;cpu_percentage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;u8&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;cpu_percentage_relative&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;u8&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;memory_percentage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;u8&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;metrics_strategy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;PodMetricsStrategy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This allows for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Fine-grained control over resource utilization&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Better handling of CPU quota management&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;More accurate memory tracking&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Customizable metrics aggregation strategies&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Real-World Impact
&lt;/h2&gt;

&lt;p&gt;These improvements have had significant real-world impact:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;30% reduction in unnecessary scaling operations&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;More stable performance under varying load conditions&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Reduced resource usage in the orchestrator itself&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Better handling of microservices with varying performance characteristics&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Still managed to retain a &amp;lt;5MB binary size footprint&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What's Next: Decentralized Clustering!?
&lt;/h2&gt;

&lt;p&gt;We're excited to explore our next major development focus: a decentralized clustering solution. This will allow Orbit to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Operate without a central control plane&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Provide better resilience in edge deployments&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Enable peer-to-peer node coordination&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Support dynamic cluster topology changes&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We have some initial ideas on how to design the solution, so please follow for our next update to see how we hope to make this happen!&lt;/p&gt;

&lt;h2&gt;
  
  
  Building at Scale with Air Pipe
&lt;/h2&gt;

&lt;p&gt;While Orbit handles container orchestration, it's just one piece of the puzzle. At &lt;a href="https://airpipe.io" rel="noopener noreferrer"&gt;Air Pipe&lt;/a&gt;, we're building a comprehensive platform for creating scalable, resilient APIs, integrations, and workflows. Our platform enables you to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Build and deploy scalable APIs with minimal boilerplate&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create robust integration workflows&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Implement resilient data processing pipelines&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Leverage edge computing capabilities&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're building distributed systems or scalable applications, visit &lt;a href="https://airpipe.io" rel="noopener noreferrer"&gt;airpipe.io&lt;/a&gt; to learn how our platform can accelerate your development.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get Involved
&lt;/h2&gt;

&lt;p&gt;We're building Orbit in the open and value community input. Whether you're interested in the technical details or want to contribute to our upcoming clustering features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Star us on &lt;a href="https://github.com/airpipeio/orbit" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Join our &lt;a href="https://discord.com/invite/b8mFtjWXZj" rel="noopener noreferrer"&gt;Discord community&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Visit &lt;a href="https://airpipe.io" rel="noopener noreferrer"&gt;Air Pipe&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Follow our progress as we build the decentralized clustering solution&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Stay tuned for our next technical deep-dive where we'll explore the architecture of our decentralized clustering approach!&lt;/p&gt;

</description>
      <category>rust</category>
      <category>containers</category>
      <category>opensource</category>
      <category>devops</category>
    </item>
    <item>
      <title>Building Orbit: A Lightweight Container Orchestration Tool in Rust</title>
      <dc:creator>Kav Pather</dc:creator>
      <pubDate>Thu, 30 Jan 2025 14:43:20 +0000</pubDate>
      <link>https://dev.to/airpipe/building-orbit-a-lightweight-container-orchestration-tool-in-rust-1072</link>
      <guid>https://dev.to/airpipe/building-orbit-a-lightweight-container-orchestration-tool-in-rust-1072</guid>
      <description>&lt;p&gt;At &lt;a href="https://airpipe.io/" rel="noopener noreferrer"&gt;Air Pipe&lt;/a&gt;, we faced a common challenge in the world of container orchestration - finding the right balance between functionality and complexity. While Kubernetes offers a powerful but complex solution, and Docker Swarm lacks crucial features like autoscaling, we needed something that fit perfectly in the middle. We decided to build &lt;a href="https://github.com/AirPipeIO/orbit" rel="noopener noreferrer"&gt;Orbit&lt;/a&gt;, a lightweight container orchestration tool written in Rust that we're actively developing and improving.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why We Started
&lt;/h2&gt;

&lt;p&gt;Our journey began with a specific need at Air Pipe. Running a shared-nothing architecture at the edge, we needed a simple way to scale HTTP/TCP based containers without the overhead of complex infrastructure or additional dependencies. We wanted something that could be deployed as a single binary, yet provide robust container management capabilities.&lt;/p&gt;

&lt;p&gt;The existing solutions didn't quite fit:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Kubernetes: Too complex for our edge use case, with a large learning curve and resource footprint&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Docker Swarm: Too basic, lacking crucial features like autoscaling&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Other solutions: Either too complex or too tightly coupled with specific cloud providers or vendors&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Starting Simple: Basic Container Management
&lt;/h2&gt;

&lt;p&gt;We started with the basics - a simple Rust application that could manage individual containers. The choice of Rust was deliberate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Memory safety without garbage collection&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;High performance and low resource usage&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Strong type system and excellent concurrency model&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Growing ecosystem of libraries and tools&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Our first version could do basic container operations:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;start_containers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;service_name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;containers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Container&lt;/span&gt;&lt;span class="o"&gt;&amp;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;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Basic container startup logic&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Evolution to Pod-Based Architecture
&lt;/h2&gt;

&lt;p&gt;As we developed the system, we realized that managing groups of containers together (pods) would provide better isolation and resource management. This led to a more sophisticated container management system:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;InstanceMetadata&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Uuid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;created_at&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;SystemTime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;network&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;containers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;ContainerMetadata&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;image_hash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;HashMap&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This structure allowed us to track related containers and their metadata together, making operations like scaling and updates more coherent.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Game Changer: Integrating Pingora
&lt;/h2&gt;

&lt;p&gt;One of our most significant technical decisions was choosing &lt;a href="https://blog.cloudflare.com/pingora-open-source/" rel="noopener noreferrer"&gt;Cloudflare's Pingora&lt;/a&gt; framework for our proxy layer. This wasn't just about load balancing - it was about building a robust, high-performance proxy system that could handle production workloads efficiently.&lt;/p&gt;

&lt;p&gt;Why Pingora?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Written in Rust, aligning with our technology stack&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Battle-tested at Cloudflare's scale&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Excellent performance characteristics&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Built-in health checking and automatic failover&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The integration looked something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;web-service&lt;/span&gt;
&lt;span class="na"&gt;instance_count&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;min&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
  &lt;span class="na"&gt;max&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;web&lt;/span&gt;
      &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;airpipeio/infoapp:latest&lt;/span&gt;
      &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
          &lt;span class="na"&gt;node_port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;30080&lt;/span&gt;  &lt;span class="c1"&gt;# This enables Pingora proxy&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Adding Intelligence: Resource-Based Autoscaling
&lt;/h2&gt;

&lt;p&gt;One of our proudest features is the intelligent autoscaling system. Instead of just scaling based on simple metrics, we implemented a sophisticated resource monitoring and scaling system:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;resource_thresholds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;cpu_percentage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;         &lt;span class="c1"&gt;# CPU usage threshold&lt;/span&gt;
  &lt;span class="na"&gt;cpu_percentage_relative&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;90&lt;/span&gt; &lt;span class="c1"&gt;# CPU usage relative to limit&lt;/span&gt;
  &lt;span class="na"&gt;memory_percentage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;85&lt;/span&gt;      &lt;span class="c1"&gt;# Memory usage threshold&lt;/span&gt;
  &lt;span class="na"&gt;metrics_strategy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;max&lt;/span&gt;      &lt;span class="c1"&gt;# Strategy for pod metrics&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The system continuously monitors these metrics and makes scaling decisions based on configurable thresholds. Here's a practical example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;demo-service&lt;/span&gt;
&lt;span class="na"&gt;instance_count&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;min&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
  &lt;span class="na"&gt;max&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;
&lt;span class="na"&gt;memory_limit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1Gi"&lt;/span&gt;
&lt;span class="na"&gt;cpu_limit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1.0"&lt;/span&gt;
&lt;span class="na"&gt;resource_thresholds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;cpu_percentage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
  &lt;span class="na"&gt;memory_percentage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;85&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;infoapp&lt;/span&gt;
      &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;airpipeio/infoapp:latest&lt;/span&gt;
      &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
          &lt;span class="na"&gt;node_port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;30080&lt;/span&gt;
      &lt;span class="na"&gt;memory_limit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;512Mi"&lt;/span&gt;
      &lt;span class="na"&gt;cpu_limit&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0.5"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this configuration, if CPU usage exceeds 80% or memory usage exceeds 85% across instances, Orbit automatically scales up the service (up to the max instance count). When resource usage decreases, it scales down gracefully while maintaining the minimum instance count.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where We Are Today
&lt;/h2&gt;

&lt;p&gt;Today, Orbit stands as a testament to what's possible when you focus on solving a specific problem well. It's a single binary under 5MB that provides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Automated container scaling&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Service discovery&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;High-performance proxying&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Volume management&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Rolling updates&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Prometheus metrics integration&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try It Yourself
&lt;/h2&gt;

&lt;p&gt;Want to see Orbit in action? Here's a simple example to get started:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/AirPipeIO/orbit/releases" rel="noopener noreferrer"&gt;Download Orbit from our releases&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create a configuration file (e.g., &lt;code&gt;web-service.yml&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;web-service&lt;/span&gt;
&lt;span class="na"&gt;instance_count&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;min&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
  &lt;span class="na"&gt;max&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;
&lt;span class="na"&gt;resource_thresholds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;cpu_percentage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
  &lt;span class="na"&gt;memory_percentage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;85&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;web&lt;/span&gt;
      &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;airpipeio/infoapp:latest&lt;/span&gt;
      &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
          &lt;span class="na"&gt;node_port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;30080&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Start Orbit: &lt;code&gt;orbit -c /path/to/configs&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Visit &lt;a href="http://localhost:30080" rel="noopener noreferrer"&gt;&lt;code&gt;http://localhost:30080&lt;/code&gt;&lt;/a&gt; to see your service in action, and watch &lt;a href="https://github.com/AirPipeIO/orbit" rel="noopener noreferrer"&gt;Orbit&lt;/a&gt; automatically manage scaling based on resource usage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Looking Forward
&lt;/h2&gt;

&lt;p&gt;We're continuing to evolve &lt;a href="https://github.com/AirPipeIO/orbit" rel="noopener noreferrer"&gt;Orbit&lt;/a&gt; based on real-world usage and community feedback. Our focus remains on maintaining simplicity while adding powerful features that make container orchestration easier and more efficient.&lt;/p&gt;

&lt;p&gt;Want to learn more or contribute? Check out our &lt;a href="https://github.com/airpipeio/orbit" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt; or join our &lt;a href="https://discord.com/invite/b8mFtjWXZj" rel="noopener noreferrer"&gt;Discord community&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  About Air Pipe
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/AirPipeIO/orbit" rel="noopener noreferrer"&gt;Orbit&lt;/a&gt; is just one piece of the puzzle in our mission to simplify and improve the software development lifecycle. At &lt;a href="https://airpipe.io" rel="noopener noreferrer"&gt;Air Pipe&lt;/a&gt;, we're building tools and platforms that make it easier to build, test, and deploy applications. Our shared-nothing architecture and edge computing approach allows for highly efficient and scalable deployments.&lt;/p&gt;

&lt;p&gt;Want to learn more about how &lt;a href="https://airpipe.io" rel="noopener noreferrer"&gt;Air Pipe&lt;/a&gt; can help streamline your development workflow? Visit &lt;a href="http://airpipe.io" rel="noopener noreferrer"&gt;airpipe.io&lt;/a&gt; to discover our complete suite of development tools and solutions. See how our platform can help you build better software, faster.&lt;/p&gt;

&lt;p&gt;💡 Visit &lt;a href="https://airpipe.io" rel="noopener noreferrer"&gt;Air Pipe&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;💡 Find us on &lt;a href="https://discord.com/invite/b8mFtjWXZj" rel="noopener noreferrer"&gt;Discord&lt;/a&gt;&lt;/p&gt;

</description>
      <category>rust</category>
      <category>containers</category>
      <category>opensource</category>
      <category>devops</category>
    </item>
    <item>
      <title>Build Your Own AI API With Google Gemini and Air Pipe</title>
      <dc:creator>Kav Pather</dc:creator>
      <pubDate>Sun, 29 Sep 2024 18:11:06 +0000</pubDate>
      <link>https://dev.to/airpipe/build-your-own-ai-api-with-google-gemini-and-air-pipe-1eii</link>
      <guid>https://dev.to/airpipe/build-your-own-ai-api-with-google-gemini-and-air-pipe-1eii</guid>
      <description>&lt;p&gt;Artificial Intelligence is at the forefront of innovation, and with Google Gemini AI, developers can unlock advanced capabilities for their applications. However, creating a custom API to tap into Geminis full power can seem complex without the right tools.&lt;/p&gt;

&lt;p&gt;Thats where &lt;strong&gt;Air Pipe&lt;/strong&gt; comes in. Air Pipe is a fast, scalable, and secure API platform that simplifies the process of building, deploying, and managing APIs. In this guide, well show you how to leverage Google Gemini AIs API and Air Pipe to create your own AI-powered API in just a few easy steps.&lt;/p&gt;

&lt;p&gt;Whether you're looking to automate tasks, enhance your app with AI, or streamline data processing, this tutorial will walk you through generating a Google Gemini API key and configuring a simple yet powerful API using Air Pipe. By the end, you'll have a fully functioning API ready to deployallowing you to integrate cutting-edge AI with ease.&lt;/p&gt;

&lt;p&gt;Lets dive in and build something amazing with Google Gemini and Air Pipe!&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Step-by-Step Guide to Build Your Own AI API&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;This tutorial will focus on the cloud platform deployment but will also work seamlessly for self hosted deployments, you can learn how to self host here &lt;a href="https://docs.airpipe.io/docs/tutorial/setup" rel="noopener noreferrer"&gt;&lt;strong&gt;https://docs.airpipe.io/docs/tutorial/setup&lt;/strong&gt;&lt;/a&gt; .&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prerequisites:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://app.airpipe.io/register" rel="noopener noreferrer"&gt;&lt;strong&gt;Sign up&lt;/strong&gt;&lt;/a&gt; or &lt;a href="https://app.airpipe.io/login" rel="noopener noreferrer"&gt;&lt;strong&gt;log in&lt;/strong&gt;&lt;/a&gt; to Air Pipe.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Google account and API Key&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 0: Get your Google Gemini API Key
&lt;/h3&gt;

&lt;p&gt;Navigate to &lt;a href="https://aistudio.google.com/app/apikey" rel="noopener noreferrer"&gt;https://aistudio.google.com/app/apikey&lt;/a&gt; and select Create API Key.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2dfk3ojf7cbh0ltja1ok.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2dfk3ojf7cbh0ltja1ok.png" alt="Get Gemini API Key"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Create a new project if you have not already.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2420u95m24b7k52i0tfn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2420u95m24b7k52i0tfn.png" alt="Create New Google Project"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;For production deployments we understand your API keys are sensitive, we provide encrypted variable (secret) support &lt;a href="https://docs.airpipe.io/docs/configuration/managed-variables" rel="noopener noreferrer"&gt;https://docs.airpipe.io/docs/configuration/managed-variables&lt;/a&gt; .&lt;/p&gt;

&lt;p&gt;The examples below will utilize this system storing the key as &lt;code&gt;gemini-key&lt;/code&gt; which is then accessed with &lt;code&gt;a|ap_var::gemini-key|&lt;/code&gt; .&lt;/p&gt;

&lt;p&gt;Other secret stores are also supported.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2cs6krjqwrbgxjvmoo1c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2cs6krjqwrbgxjvmoo1c.png" alt="Gemini API Key"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;Step 1: Create your Configuration&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Once signed in to your Air Pipe account, head to &lt;a href="https://app.airpipe.io/configurations" rel="noopener noreferrer"&gt;&lt;strong&gt;Configurations&lt;/strong&gt;&lt;/a&gt; &amp;gt; &lt;strong&gt;Add New&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq5dfyg44jrokdjj9v57z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq5dfyg44jrokdjj9v57z.png" alt="Add Configuration"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Create a HTTP POST interface
&lt;/h3&gt;

&lt;p&gt;In the YAML editor paste the below example. Here we simply define a HTTP POST interface and route called &lt;strong&gt;gemini/query&lt;/strong&gt;. We further define our first &lt;a href="https://docs.airpipe.io/docs/configuration/interfaces/actions" rel="noopener noreferrer"&gt;action&lt;/a&gt; to check the body of the payload for a &lt;strong&gt;text&lt;/strong&gt; attribute to exist and &lt;a href="https://docs.airpipe.io/docs/configuration/interfaces/actions/asserts" rel="noopener noreferrer"&gt;assert&lt;/a&gt; that it must be less than 20 characters (this is a simple example for illustrative purposes).&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://docs.airpipe.io/docs/configuration/interfaces/actions/asserts" rel="noopener noreferrer"&gt;Assertions&lt;/a&gt; are important to understand as we can control what the user is allowed to input, and then we can further control the outcome of the API and action with the configured &lt;a href="https://docs.airpipe.io/docs/configuration/interfaces/actions/asserts/functions" rel="noopener noreferrer"&gt;tests&lt;/a&gt; .&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AskGemini&lt;/span&gt;

&lt;span class="na"&gt;interfaces&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;gemini/query&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;# http route will be defaulted to this interface name&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;POST&lt;/span&gt;
    &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;a|body&lt;/span&gt;
    &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http&lt;/span&gt;

    &lt;span class="na"&gt;actions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;CheckBody&lt;/span&gt;
        &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;a|body&lt;/span&gt; &lt;span class="c1"&gt;# access the post body as the input to assert&lt;/span&gt;
        &lt;span class="na"&gt;assert&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;tests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;text&lt;/span&gt;
              &lt;span class="na"&gt;is_not_null&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
              &lt;span class="na"&gt;is_less_than&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20&lt;/span&gt; &lt;span class="c1"&gt;# limit the amount of characters&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: Retrieving the full Gemini Response
&lt;/h3&gt;

&lt;p&gt;Lets extend the configuration and pass the &lt;strong&gt;text&lt;/strong&gt; attribute to the Google Gemini API, we do this using &lt;code&gt;a|CheckBody::text|&lt;/code&gt; in our configuration. We will also use &lt;a href="https://docs.airpipe.io/docs/configuration/interfaces/actions/workflow-control" rel="noopener noreferrer"&gt;workflow control&lt;/a&gt;, to ensure the &lt;strong&gt;CheckBody&lt;/strong&gt; action succeeds before querying Google Gemini AI with &lt;a href="https://docs.airpipe.io/docs/configuration/interfaces/actions/workflow-control/examples#when-succeeded" rel="noopener noreferrer"&gt;&lt;code&gt;run_when_succeeded&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;We are using Action Variables to dynamically substitute the value into the payload, see our docs on &lt;a href="https://docs.airpipe.io/docs/configuration/substitutions" rel="noopener noreferrer"&gt;variable substitutions&lt;/a&gt; for further details and options.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;💡 Important! In the &lt;strong&gt;URL&lt;/strong&gt; field below we are using &lt;code&gt;a|ap_var::gemini-key|&lt;/code&gt; this implies we have a variable stored on the Air Pipe platform named &lt;code&gt;gemini-key&lt;/code&gt; to access. See &lt;a href="https://docs.airpipe.io/docs/configuration/managed-variables" rel="noopener noreferrer"&gt;Managed Variables&lt;/a&gt; if youd like to do the same. Otherwise update this with your key or use your own secret store or environment variable if self hosting.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AskGemini&lt;/span&gt;

&lt;span class="na"&gt;interfaces&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;gemini/query&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;POST&lt;/span&gt;
    &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;a|body&lt;/span&gt;
    &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http&lt;/span&gt;

    &lt;span class="na"&gt;actions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;CheckBody&lt;/span&gt;
        &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;a|body&lt;/span&gt;
        &lt;span class="na"&gt;assert&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;tests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;text&lt;/span&gt;
              &lt;span class="na"&gt;is_not_null&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
              &lt;span class="na"&gt;is_less_than&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20&lt;/span&gt; &lt;span class="c1"&gt;# limit the amount of characters&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;QueryGemini&lt;/span&gt;
        &lt;span class="na"&gt;run_when_succeeded&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;previous&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt; &lt;span class="c1"&gt;# only run after CheckBody succeeds&lt;/span&gt;
        &lt;span class="na"&gt;http&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
          &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash-latest:generateContent?key=a|ap_var::gemini-key|&lt;/span&gt;
          &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;POST&lt;/span&gt;
          &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;content-type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;application/json&lt;/span&gt;
          &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt; &lt;span class="c1"&gt;# substitute the 'text' we just validated in 'CheckBody' into the body payload&lt;/span&gt;
            &lt;span class="s"&gt;{"contents":[{"parts":[{"text":"a|CheckBody::text|"}]}]}&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;strong&gt;Step 4: Deploy and Test&lt;/strong&gt;
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Set your permission as &lt;strong&gt;USER_WITH_PUBLIC_READ&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Select &lt;strong&gt;Deploy to Staging&lt;/strong&gt; and &lt;strong&gt;Deploy to Production&lt;/strong&gt; and hit create.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnenzf6d5m3asix97nafc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnenzf6d5m3asix97nafc.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Use your favorite API testing tool, or do a curl to your endpoints, lets try the &lt;code&gt;gemini/query&lt;/code&gt; route, make sure you update your organization uuid in the route.&lt;/p&gt;

&lt;p&gt;You can find your &lt;strong&gt;organization uuid&lt;/strong&gt; under &lt;a href="https://app.airpipe.io/configurations" rel="noopener noreferrer"&gt;&lt;strong&gt;Configurations&lt;/strong&gt;&lt;/a&gt; or in your &lt;a href="https://app.airpipe.io/organization/settings" rel="noopener noreferrer"&gt;&lt;strong&gt;Organization Settings.&lt;/strong&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvnkylncbwuh3x8e5z904.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvnkylncbwuh3x8e5z904.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Send a HTTP POST with a JSON payload eg. &lt;code&gt;{"text": "tell me about dogs"}&lt;/code&gt; to &lt;code&gt;gemini/query&lt;/code&gt; to retrieve the response.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Send a HTTP POST with a JSON payload eg. &lt;code&gt;{"text": "tell me about really big dogs"}&lt;/code&gt; to test the length condition triggering a failure and preventing the &lt;strong&gt;QueryGemini&lt;/strong&gt; action from proceeded.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://api.airpipe.io/YOUR-ORGANIZATION-UUID/production/gemini/query" rel="noopener noreferrer"&gt;&lt;strong&gt;https://api.airpipe.io/YOUR-ORGANIZATION-UUID/production/gemini/query&lt;/strong&gt;&lt;/a&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;--location&lt;/span&gt; &lt;span class="s1"&gt;'https://api.airpipe.io/YOUR-ORGANIZATION-UUID/production/gemini/query'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--data&lt;/span&gt; &lt;span class="s1"&gt;'{
    "text": "tell me about dogs"
}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"CheckBody"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"time.ms"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tell me about dogs"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"success"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"QueryGemini"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"time.ms"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4180&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"headers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"content-type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"application/json; charset=UTF-8"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"vary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Origin,Accept-Encoding"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"date"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Sun, 29 Sep 2024 15:40:42 GMT"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"server"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"scaffolding on HTTPServer2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"cache-control"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"private"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"x-xss-protection"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"x-frame-options"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"SAMEORIGIN"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"x-content-type-options"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"nosniff"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"server-timing"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"gfet4t7; dur=4016"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"alt-svc"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"h3=&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:443&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;; ma=2592000,h3-29=&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;:443&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;; ma=2592000"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"accept-ranges"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"none"&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"body"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"candidates"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                            &lt;/span&gt;&lt;span class="nl"&gt;"content"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                                &lt;/span&gt;&lt;span class="nl"&gt;"parts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
                                    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                                        &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"## Dogs: Man's Best Friend, and More!&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;Dogs, those furry, wagging companions, have been by our side for thousands of years. They're more than just pets; they're loyal friends, helpful assistants, and sometimes even family members. Let's delve into what makes dogs so special:&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;**Origins and History:**&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;* **Wolf Ancestry:** Domestic dogs are descended from wolves, with the earliest evidence of domestication dating back 15,000 years.&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;* **Evolution:** Over time, dogs evolved to work alongside humans in various roles like hunting, herding, and guarding. This led to the development of different breeds, each with unique physical and behavioral characteristics.&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;**Breed Diversity:**&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;* **Over 300 Breeds:** The world boasts a fascinating variety of dog breeds, each with a distinct appearance, temperament, and purpose.&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;* **Classifications:** Breeds are categorized based on their size, coat type, and intended roles, like working, herding, sporting, and companion dogs. &lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;* **Unique Traits:** From tiny Chihuahuas to massive Great Danes, each breed has its own personality and quirks.&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;**Personality and Behavior:**&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;* **Social Creatures:** Dogs are highly social animals, forming strong bonds with their human families.&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;* **Intelligence and Trainability:** Many dogs are incredibly intelligent and capable of learning complex commands and tricks. &lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;* **Emotionally Expressive:** Dogs communicate their emotions through body language, vocalizations, and facial expressions.&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;* **Individual Differences:**  While certain breeds tend to have specific temperament traits, each dog is an individual with their own unique personality.&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;**Health and Care:**&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;* **Lifespan:** Depending on the breed, dogs can live anywhere from 10 to 20 years.&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;* **Nutrition and Exercise:** Dogs require a balanced diet and regular exercise to stay healthy and happy.&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;* **Grooming:** Different breeds have different grooming needs, ranging from occasional brushing to daily baths.&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;* **Veterinary Care:** Regular checkups, vaccinations, and preventative measures are crucial for maintaining good health.&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;**Beyond Companionship:**&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;* **Working Dogs:** Dogs play vital roles in various professions, including law enforcement, search and rescue, assistance for individuals with disabilities, and therapy work.&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;* **Emotional Support:** Dogs provide emotional support, reduce stress, and improve mental well-being.&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;* **Family Members:** For many families, dogs become cherished members, offering unconditional love, companionship, and joy.&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;**Getting a Dog:**&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;* **Research:** Before getting a dog, consider your lifestyle, living situation, and the breed's temperament and care requirements.&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;* **Adopting:** Consider adopting a dog from a shelter or rescue organization, giving a loving home to a dog in need.&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;* **Responsible Ownership:** Be prepared to provide a dog with a safe, loving environment, proper care, and training.&lt;/span&gt;&lt;span class="se"&gt;\n\n&lt;/span&gt;&lt;span class="s2"&gt;Dogs enrich our lives in countless ways. They offer companionship, loyalty, and unconditional love, making them truly deserving of their title: &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;Man's Best Friend.&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt;
                                    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
                                &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
                                &lt;/span&gt;&lt;span class="nl"&gt;"role"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"model"&lt;/span&gt;&lt;span class="w"&gt;
                            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
                            &lt;/span&gt;&lt;span class="nl"&gt;"finishReason"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"STOP"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                            &lt;/span&gt;&lt;span class="nl"&gt;"index"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                            &lt;/span&gt;&lt;span class="nl"&gt;"safetyRatings"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
                                &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                                    &lt;/span&gt;&lt;span class="nl"&gt;"category"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"HARM_CATEGORY_SEXUALLY_EXPLICIT"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                                    &lt;/span&gt;&lt;span class="nl"&gt;"probability"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"NEGLIGIBLE"&lt;/span&gt;&lt;span class="w"&gt;
                                &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
                                &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                                    &lt;/span&gt;&lt;span class="nl"&gt;"category"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"HARM_CATEGORY_HATE_SPEECH"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                                    &lt;/span&gt;&lt;span class="nl"&gt;"probability"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"NEGLIGIBLE"&lt;/span&gt;&lt;span class="w"&gt;
                                &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
                                &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                                    &lt;/span&gt;&lt;span class="nl"&gt;"category"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"HARM_CATEGORY_HARASSMENT"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                                    &lt;/span&gt;&lt;span class="nl"&gt;"probability"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"NEGLIGIBLE"&lt;/span&gt;&lt;span class="w"&gt;
                                &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
                                &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                                    &lt;/span&gt;&lt;span class="nl"&gt;"category"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"HARM_CATEGORY_DANGEROUS_CONTENT"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                                    &lt;/span&gt;&lt;span class="nl"&gt;"probability"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"NEGLIGIBLE"&lt;/span&gt;&lt;span class="w"&gt;
                                &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
                            &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"usageMetadata"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="nl"&gt;"promptTokenCount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="nl"&gt;"candidatesTokenCount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;642&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="nl"&gt;"totalTokenCount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;646&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 5: Clean up the Gemini Response
&lt;/h3&gt;

&lt;p&gt;As you can see with the response returned above, Google Gemini returns a lot of additional data you do not need to expose to your end users. We can use &lt;a href="https://0feaf97f.dummy-ap.pages.dev/docs/configuration/interfaces/actions/transforms/functions" rel="noopener noreferrer"&gt;&lt;code&gt;post_transforms&lt;/code&gt;&lt;/a&gt; with &lt;a href="https://0feaf97f.dummy-ap.pages.dev/docs/configuration/interfaces/actions/transforms/functions#extract-value" rel="noopener noreferrer"&gt;&lt;code&gt;extract_value&lt;/code&gt;&lt;/a&gt; to target the &lt;code&gt;text&lt;/code&gt; attribute to return.&lt;/p&gt;

&lt;p&gt;Go to &lt;a href="https://app.airpipe.io/configurations" rel="noopener noreferrer"&gt;Configurations&lt;/a&gt; and find your &lt;strong&gt;AskGemini&lt;/strong&gt; configuration and click update, and paste the below.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;View &lt;a href="https://docs.airpipe.io/docs/configuration/interfaces/actions/transforms/functions" rel="noopener noreferrer"&gt;Transforms &amp;amp; Functions&lt;/a&gt; for further ways to manipulate your data.&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AskGemini&lt;/span&gt;

&lt;span class="na"&gt;interfaces&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;gemini/query&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;POST&lt;/span&gt;
    &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;a|body&lt;/span&gt;
    &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http&lt;/span&gt;

    &lt;span class="na"&gt;actions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;CheckBody&lt;/span&gt;
        &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;a|body&lt;/span&gt;
        &lt;span class="na"&gt;assert&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;tests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;text&lt;/span&gt;
              &lt;span class="na"&gt;is_not_null&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
              &lt;span class="na"&gt;is_less_than&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20&lt;/span&gt; &lt;span class="c1"&gt;# limit the amount of characters&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;QueryGemini&lt;/span&gt;
        &lt;span class="na"&gt;run_when_succeeded&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;previous&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt; &lt;span class="c1"&gt;# only run after CheckBody succeeds&lt;/span&gt;
        &lt;span class="na"&gt;http&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
          &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash-latest:generateContent?key=a|ap_var::gemini-key|&lt;/span&gt;
          &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;POST&lt;/span&gt;
          &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;content-type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;application/json&lt;/span&gt;
          &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt; &lt;span class="c1"&gt;# substitute the 'text' we just validated in 'CheckBody' into the body payload&lt;/span&gt;
            &lt;span class="s"&gt;{"contents":[{"parts":[{"text":"a|CheckBody::text|"}]}]} &lt;/span&gt;
        &lt;span class="na"&gt;post_transforms&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;extract_value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.body.candidates[0].content.parts[0].text"&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;    {
        "data": {
            "CheckBody": {
                "time.ms": 0,
                "data": {
                    "text": "tell me about dogs"
                },
                "message": "success"
            },
            "QueryGemini": {
                "time.ms": 4111,
                "data": "## Dogs: The Best Friends of Humans\n\nDogs, also known as **Canis lupus familiaris**, are domesticated mammals that have been companions to humans for thousands of years. Their loyalty, intelligence, and playful nature have made them beloved pets worldwide.\n\n**Here's a breakdown of some key aspects of dogs:**\n\n**Physical Characteristics:**\n\n* **Size:** Dogs come in a wide range of sizes, from tiny Chihuahuas to massive Great Danes.\n* **Coat:** Dog coats vary in length, texture, and color. Some have short, smooth coats, while others have long, fluffy fur.\n* **Tail:** Most dogs have tails that wag when they are happy. Tail length and shape can vary depending on the breed.\n* **Ears:** Dog ears come in various shapes and sizes, from floppy to erect.\n* **Snout:** Dogs have a strong sense of smell, which is aided by their long, pointed snouts.\n\n**Behavior:**\n\n* **Intelligence:** Dogs are highly intelligent animals, capable of learning complex commands and tasks.\n* **Socialization:** Dogs are social animals that thrive on interaction with humans and other dogs.\n* **Loyalty:** Dogs are known for their loyalty and devotion to their owners.\n* **Playfulness:** Dogs are playful animals that enjoy games and activities.\n* **Training:** Dogs are highly trainable, and with proper training, they can learn a wide range of behaviors.\n\n**Breeds:**\n\nThere are over 300 recognized dog breeds, each with its own unique physical characteristics and temperament. Some popular breeds include:\n\n* **Golden Retriever:** Friendly, intelligent, and eager to please.\n* **Labrador Retriever:** Loyal, playful, and known for their love of water.\n* **German Shepherd:** Intelligent, loyal, and protective.\n* **Poodle:** Intelligent, hypoallergenic, and known for their curly coats.\n* **French Bulldog:** Affectionate, playful, and known for their bat ears.\n\n**Health:**\n\n* **Lifespan:** The average lifespan of a dog is 10-13 years, but some breeds can live longer.\n* **Diet:** Dogs need a balanced diet that is rich in protein, carbohydrates, and essential nutrients.\n* **Exercise:** Most dogs need regular exercise to stay healthy and happy.\n* **Veterinary care:** Regular veterinary checkups and vaccinations are essential for maintaining a dog's health.\n\n**Benefits of Owning a Dog:**\n\n* **Companionship:** Dogs provide companionship and unconditional love.\n* **Stress relief:** Owning a dog can help reduce stress and anxiety.\n* **Exercise and activity:** Dogs encourage their owners to be more active.\n* **Improved mental health:** Studies have shown that owning a dog can improve mental health.\n* **Protection:** Some breeds are known for their protective instincts.\n\n**Conclusion:**\n\nDogs are wonderful companions that offer many benefits to their owners. Whether you're looking for a loyal friend, a playful companion, or a furry family member, there's a dog breed out there for everyone."
            }
        }
    }

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 6: Restricting and manipulating the text prompt
&lt;/h3&gt;

&lt;p&gt;As youve seen before we can restrict the input text with certain conditions. However we may also want to restrict what we are requesting from Google Gemini AI, or want it to behave or respond in a particular way.&lt;/p&gt;

&lt;p&gt;In our example lets say we want a maximum of 100 characters returned only.&lt;/p&gt;

&lt;p&gt;We can simply modify the body we are sending to Google Gemini AI to prefix the &lt;code&gt;text&lt;/code&gt; with some additional text eg. &lt;code&gt;Your response to the following question should be less than 100 characters: a|CheckBody::text|&lt;/code&gt; .&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"contents"&lt;/span&gt;&lt;span class="p"&gt;:[{&lt;/span&gt;&lt;span class="nl"&gt;"parts"&lt;/span&gt;&lt;span class="p"&gt;:[{&lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"Your response to the following question should be less than 100 characters: a|CheckBody::text|"&lt;/span&gt;&lt;span class="p"&gt;}]}]}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Complete example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AskGemini&lt;/span&gt;

&lt;span class="na"&gt;interfaces&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;gemini/query&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;POST&lt;/span&gt;
    &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;a|body&lt;/span&gt;
    &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http&lt;/span&gt;

    &lt;span class="na"&gt;actions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;CheckBody&lt;/span&gt;
        &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;a|body&lt;/span&gt;
        &lt;span class="na"&gt;assert&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;tests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;text&lt;/span&gt;
              &lt;span class="na"&gt;is_not_null&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
              &lt;span class="na"&gt;is_less_than&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;20&lt;/span&gt; &lt;span class="c1"&gt;# limit the amount of input characters&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;QueryGemini&lt;/span&gt;
        &lt;span class="na"&gt;run_when_succeeded&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;previous&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt; &lt;span class="c1"&gt;# only run after CheckBody succeeds&lt;/span&gt;
        &lt;span class="na"&gt;http&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;# store your gemini key in the hosted variables or substitute a|ap_var::gemini-key| with your key if testing # https://aistudio.google.com/app/apikey&lt;/span&gt;
          &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash-latest:generateContent?key=a|ap_var::gemini-key|&lt;/span&gt;
          &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;POST&lt;/span&gt;
          &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;content-type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;application/json&lt;/span&gt;
          &lt;span class="na"&gt;body&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt; &lt;span class="c1"&gt;# substitute the 'text' we just validated in 'CheckBody' into the body payload&lt;/span&gt;
            &lt;span class="s"&gt;{"contents":[{"parts":[{"text":"Your response to the following question should be less than 100 characters: a|CheckBody::text|"}]}]} &lt;/span&gt;
        &lt;span class="na"&gt;post_transforms&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;extract_value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.body.candidates[0].content.parts[0].text"&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"CheckBody"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"time.ms"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"text"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"tell me about dogs"&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"message"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"success"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"QueryGemini"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"time.ms"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1907&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Dogs are loyal, furry companions known for their intelligence and love. 🐶 &lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you finally have a nice clean response from Google Gemini AI for your end users to consume. We have securely stored the API key, validated the input text, cleaned the data and effectively sandboxed the output response.&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;So with that youre done! Whats Next?&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Air Pipe provides you a fast, consistent and robust development experience that you can continue extending with confidence across anything you build.&lt;/p&gt;

&lt;p&gt;You can further extend your configuration to connect to &lt;a href="https://docs.airpipe.io/docs/tutorial/database-to-api" rel="noopener noreferrer"&gt;databases&lt;/a&gt; and other endpoints all in the one configuration with workflow controls at your disposal.&lt;/p&gt;

&lt;p&gt;See:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://docs.airpipe.io/docs/tutorial/database-to-api" rel="noopener noreferrer"&gt;Databases&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://docs.airpipe.io/docs/tutorial/setup" rel="noopener noreferrer"&gt;Tutorial&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://docs.airpipe.io/docs/configuration" rel="noopener noreferrer"&gt;Configuration&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://docs.airpipe.io/docs/tutorial/validating-data" rel="noopener noreferrer"&gt;Validating Data&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Performance and Reliability Built on Rust
&lt;/h3&gt;

&lt;p&gt;One of the core strengths of Air Pipe is that it is built on &lt;strong&gt;Rust&lt;/strong&gt; , known for its &lt;strong&gt;memory safety&lt;/strong&gt; , &lt;strong&gt;thread safety&lt;/strong&gt; , and &lt;strong&gt;high performance&lt;/strong&gt;. Whether you're managing large-scale databases or smaller applications, Rust ensures your APIs run smoothly and securely.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Google Gemini?
&lt;/h3&gt;

&lt;p&gt;Google Gemini AI is a powerful platform for advanced natural language processing and machine learning, ideal for building intelligent, scalable applications. Its real-time capabilities and seamless integration make it a top choice for AI-driven services.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;integrating Google Gemini AI with Air Pipe allows developers to build powerful, scalable APIs with ease. By combining Gemini's advanced AI capabilities with Air Pipe's speed, security, and simple deployment, you can create intelligent applications that handle complex tasks efficiently.&lt;/p&gt;

&lt;p&gt;Whether you're automating workflows or enhancing data analysis, this seamless integration empowers you to innovate faster. Start building your API today and unlock new possibilities with Air Pipe and Google Gemini AI.&lt;/p&gt;

&lt;p&gt;Ready to get started? Head over to &lt;a href="https://airpipe.io/" rel="noopener noreferrer"&gt;&lt;strong&gt;Air Pipe&lt;/strong&gt;&lt;/a&gt; and build your first Gemini API in minutes.&lt;/p&gt;

&lt;p&gt;💡Visit &lt;a href="https://airpipe.io/" rel="noopener noreferrer"&gt;Air Pipe&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;💡Find us on &lt;a href="https://discord.com/invite/b8mFtjWXZj" rel="noopener noreferrer"&gt;Discord&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;💡&lt;a href="https://docs.airpipe.io/docs/examples/Google/gemini-ai" rel="noopener noreferrer"&gt;Google Gemini AI Examples&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;💡&lt;a href="https://ai.google.dev/api/all-methods" rel="noopener noreferrer"&gt;https://ai.google.dev/api/all-methods&lt;/a&gt;&lt;/p&gt;

</description>
      <category>api</category>
      <category>gemini</category>
      <category>ai</category>
      <category>devops</category>
    </item>
    <item>
      <title>Build MongoDB APIs in 30 Seconds with Air Pipe</title>
      <dc:creator>Kav Pather</dc:creator>
      <pubDate>Fri, 20 Sep 2024 04:15:33 +0000</pubDate>
      <link>https://dev.to/airpipe/build-mongodb-apis-in-30-seconds-with-air-pipe-590f</link>
      <guid>https://dev.to/airpipe/build-mongodb-apis-in-30-seconds-with-air-pipe-590f</guid>
      <description>&lt;p&gt;Are you tired of spending countless hours configuring your APIs and managing complex workflows for your MongoDB database? What if we told you that you could set up a fully functional API in just 30 seconds? In this article, well show you how Air Pipes &lt;strong&gt;cutting-edge platform&lt;/strong&gt; makes API creation fast, efficient, and hassle-free.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Air Pipe?
&lt;/h3&gt;

&lt;p&gt;Air Pipe revolutionizes API development, offering a platform where building, deploying, and managing APIs, integrations and workflows takes mere seconds.&lt;/p&gt;

&lt;p&gt;Here's why Air Pipe is a game changer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Instant Deployment&lt;/strong&gt; : Get your MongoDB APIs running in 30 seconds flat.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Secure &amp;amp; Scalable&lt;/strong&gt; : Built on Rust for memory and thread safety, ensuring your APIs are secure, fast, and scalable.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Flexible Integration&lt;/strong&gt; : Effortlessly integrate with databases like MongoDB and more, connect to HTTP endpoints, and run scripts or commands.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Deploy Anywhere&lt;/strong&gt; : Use Air Pipe's cloud platform or deploy it self-hosted on any infrastructure.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step-by-Step Guide to Building MongoDB APIs
&lt;/h3&gt;

&lt;p&gt;This tutorial will focus on the cloud platform deployment but will also work seamlessly for self hosted deployments, you can learn how to self host here &lt;a href="https://docs.airpipe.io/docs/tutorial/setup" rel="noopener noreferrer"&gt;https://docs.airpipe.io/docs/tutorial/setup&lt;/a&gt; .&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prerequisites:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://app.airpipe.io/register" rel="noopener noreferrer"&gt;Sign up&lt;/a&gt; or &lt;a href="https://app.airpipe.io/login" rel="noopener noreferrer"&gt;log in&lt;/a&gt; to Air Pipe.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;MongoDB database or if you dont have a MongoDB instance &lt;a href="https://www.mongodb.com/cloud/atlas/register" rel="noopener noreferrer"&gt;register for MongoDB Atlas&lt;/a&gt;, create a free cluster and load the sample dataset.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  &lt;strong&gt;Step 1: Create your Configuration&lt;/strong&gt;
&lt;/h4&gt;

&lt;p&gt;Once signed in to your Air Pipe account, head to &lt;strong&gt;Configurations&lt;/strong&gt; &amp;gt; &lt;strong&gt;Add New&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuhtc3zxfwu7so89srzu5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuhtc3zxfwu7so89srzu5.png" alt="Configuration Add" width="800" height="475"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2: Define your MongoDB Data Source&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Defining your data source is as simple as naming your &lt;strong&gt;database&lt;/strong&gt; , setting a &lt;strong&gt;driver&lt;/strong&gt; and adding a &lt;strong&gt;connection string&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;For production deployments we understand your connection string is sensitive, we provide encrypted variable (secret) support &lt;a href="https://docs.airpipe.io/docs/configuration/managed-variables" rel="noopener noreferrer"&gt;https://docs.airpipe.io/docs/configuration/managed-variables&lt;/a&gt; .&lt;/p&gt;

&lt;p&gt;Other secret stores are also supported.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In the YAML editor paste the below example and update the connection string.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mongodb-find-example&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my mongo api&lt;/span&gt;

&lt;span class="na"&gt;global&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;databases&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;main&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
      &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mongodb&lt;/span&gt;
      &lt;span class="na"&gt;conn_string&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
        &lt;span class="s"&gt;mongodb+srv://someUser:somePass@bob-perf.hello.mongodb.net/?retryWrites=true&amp;amp;w=majority&amp;amp;appName=my-test&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;The &lt;strong&gt;global&lt;/strong&gt; block defines the databases, variables, secrets and templates to be used across API interfaces and its subsequent actions.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  Step 3: Creating HTTP interfaces
&lt;/h4&gt;

&lt;p&gt;Its trivial to create a HTTP interface with Air Pipe, below we set a route name as &lt;strong&gt;mongodb/find&lt;/strong&gt; , the method as &lt;strong&gt;POST&lt;/strong&gt; and output to &lt;strong&gt;http&lt;/strong&gt;.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mongodb-find-example&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my mongo api&lt;/span&gt;

&lt;span class="na"&gt;global&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;databases&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;main&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
      &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mongodb&lt;/span&gt;
      &lt;span class="na"&gt;conn_string&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
        &lt;span class="s"&gt;mongodb+srv://someUser:somePass@bob-perf.hello.mongodb.net/?retryWrites=true&amp;amp;w=majority&amp;amp;appName=my-test&lt;/span&gt;

&lt;span class="na"&gt;interfaces&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;mongodb/find&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="c1"&gt;# this will be used as our route name&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;POST&lt;/span&gt; &lt;span class="c1"&gt;# method set to POST so we can accept input data&lt;/span&gt;
    &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http&lt;/span&gt; &lt;span class="c1"&gt;# output set to HTTP&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;The same concept can be used for creating webhooks and creating an entry point to any of your configurations that access databases, commands, scripts and other HTTP endpoints.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Step 4: Define your Actions&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.airpipe.io/docs/configuration/interfaces/actions" rel="noopener noreferrer"&gt;&lt;strong&gt;Actions&lt;/strong&gt;&lt;/a&gt; are a set of instructions an interface will run when called.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Select your &lt;a href="https://docs.airpipe.io/docs/tutorial/nosql-database-to-api-connecting" rel="noopener noreferrer"&gt;database&lt;/a&gt; to connect to i.e. &lt;code&gt;main&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Set the &lt;a href="https://docs.airpipe.io/docs/configuration/interfaces/actions/inputs/databases#document_operation" rel="noopener noreferrer"&gt;document operation&lt;/a&gt; and filter.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Well also add a &lt;strong&gt;mongodb/update&lt;/strong&gt; interface while were at it.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mongodb-operations&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;use any mongodb operation&lt;/span&gt;

&lt;span class="na"&gt;global&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;databases&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;main&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
      &lt;span class="na"&gt;driver&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mongodb&lt;/span&gt;
      &lt;span class="na"&gt;conn_string&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
        &lt;span class="s"&gt;mongodb+srv://someUser:somePass@bob-perf.hello.mongodb.net/?retryWrites=true&amp;amp;w=majority&amp;amp;appName=Cluster0&lt;/span&gt;

&lt;span class="na"&gt;interfaces&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;mongodb/find&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;POST&lt;/span&gt;
    &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http&lt;/span&gt;

    &lt;span class="na"&gt;actions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;MongoQuery&lt;/span&gt;
        &lt;span class="na"&gt;database&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;
        &lt;span class="na"&gt;document_operation&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;database&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sample_analytics&lt;/span&gt; 
          &lt;span class="na"&gt;collection&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;customers&lt;/span&gt;
          &lt;span class="na"&gt;operation&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;findOne&lt;/span&gt;
          &lt;span class="na"&gt;filter&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;{"name":&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;"a|body::name|"}'&lt;/span&gt;

  &lt;span class="na"&gt;mongodb/update&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;POST&lt;/span&gt;
    &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http&lt;/span&gt;

    &lt;span class="na"&gt;actions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;MongoQuery&lt;/span&gt;
        &lt;span class="na"&gt;database&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;
        &lt;span class="na"&gt;document_operation&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;database&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sample_analytics&lt;/span&gt;
          &lt;span class="na"&gt;collection&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;customers&lt;/span&gt;
          &lt;span class="na"&gt;operation&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;updateOne&lt;/span&gt;
          &lt;span class="na"&gt;filter&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;{"name":&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;"a|body::name|"}'&lt;/span&gt;
          &lt;span class="na"&gt;update&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;{"$set":&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;{"active":&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;a|body::status|}}'&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;For all available operations and settings view &lt;a href="https://docs.airpipe.io/docs/configuration/interfaces/actions/inputs/databases#document_operation" rel="noopener noreferrer"&gt;https://docs.airpipe.io/docs/configuration/interfaces/actions/inputs/databases#document_operation&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  Step 5: Deploy and Test
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Set your permission as &lt;strong&gt;USER_WITH_PUBLIC_READ&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Select &lt;strong&gt;Deploy to Staging&lt;/strong&gt; and &lt;strong&gt;Deploy to Production&lt;/strong&gt; and hit create.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkuloh66s4ufxuedemt12.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkuloh66s4ufxuedemt12.png" alt="Deploy" width="800" height="339"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Its that easy your API is live and ready to be consumed in seconds.&lt;/p&gt;

&lt;p&gt;Use your favorite API testing tool, or do a curl to your endpoints, lets try the &lt;code&gt;mongodb/find&lt;/code&gt; route, make sure you update your organization uuid in the route.&lt;/p&gt;

&lt;p&gt;You can find your &lt;strong&gt;organization uuid&lt;/strong&gt; under &lt;a href="https://app.airpipe.io/configurations" rel="noopener noreferrer"&gt;Configurations&lt;/a&gt; or in your &lt;a href="https://app.airpipe.io/organization/settings" rel="noopener noreferrer"&gt;Organization Settings.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo73moff1yewj3a00su75.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo73moff1yewj3a00su75.png" alt="Configurations List" width="800" height="441"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Send a HTTP POST with a JSON payload eg. &lt;code&gt;{"name": "Elizabeth Ray"}&lt;/code&gt; to &lt;code&gt;mongodb/find&lt;/code&gt; to find a document with a matching name.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Send a HTTP POST with a JSON payload eg. &lt;code&gt;{"name": "Elizabeth Ray", "status": false}&lt;/code&gt; to &lt;code&gt;mongodb/update&lt;/code&gt; to find a document with a matching name and update the status value accordingly.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://api.airpipe.io/f9fc628a-your-organization-UUID-ee4961020ad3/production/mongodb/find" rel="noopener noreferrer"&gt;https://api.airpipe.io/YOUR-ORGANIZATION-UUID/production/mongodb/find&lt;/a&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;--location&lt;/span&gt; &lt;span class="s1"&gt;'https://api.airpipe.io/YOUR-ORGANIZATION-UUID/production/mongodb/find'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'Content-Type: application/json'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--data&lt;/span&gt; &lt;span class="s1"&gt;'{
    "name": "Elizabeth Ray",
    "status": true
}'&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Example response:&lt;/p&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;br&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;br&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"MongoQuery"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;br&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"time.ms"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&lt;br&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;br&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;br&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"$oid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"5ca4bbcea2dd94ee58162a68"&lt;/span&gt;&lt;span class="w"&gt;&lt;br&gt;
                &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;&lt;br&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"username"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"fmiller"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&lt;br&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Elizabeth Ray"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&lt;br&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"address"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"9286 Bethany Glens&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;Vasqueztown, CO 22939"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&lt;br&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"birthdate"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;br&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"$date"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;br&gt;
                        &lt;/span&gt;&lt;span class="nl"&gt;"$numberLong"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"226117231000"&lt;/span&gt;&lt;span class="w"&gt;&lt;br&gt;
                    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;br&gt;
                &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;&lt;br&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"email"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;a href="mailto:arroyocolton@gmail.com"&gt;arroyocolton@gmail.com&lt;/a&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&lt;br&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"active"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&lt;br&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"accounts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;&lt;br&gt;
                    &lt;/span&gt;&lt;span class="mi"&gt;371138&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&lt;br&gt;
                    &lt;/span&gt;&lt;span class="mi"&gt;324287&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&lt;br&gt;
                    &lt;/span&gt;&lt;span class="mi"&gt;276528&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&lt;br&gt;
                    &lt;/span&gt;&lt;span class="mi"&gt;332179&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&lt;br&gt;
                    &lt;/span&gt;&lt;span class="mi"&gt;422649&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&lt;br&gt;
                    &lt;/span&gt;&lt;span class="mi"&gt;387979&lt;/span&gt;&lt;span class="w"&gt;&lt;br&gt;
                &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;&lt;br&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"tier_and_details"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;br&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"0df078f33aa74a2e9696e0520c1a828a"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;br&gt;
                        &lt;/span&gt;&lt;span class="nl"&gt;"tier"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Bronze"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&lt;br&gt;
                        &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0df078f33aa74a2e9696e0520c1a828a"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&lt;br&gt;
                        &lt;/span&gt;&lt;span class="nl"&gt;"active"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&lt;br&gt;
                        &lt;/span&gt;&lt;span class="nl"&gt;"benefits"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;&lt;br&gt;
                            &lt;/span&gt;&lt;span class="s2"&gt;"sports tickets"&lt;/span&gt;&lt;span class="w"&gt;&lt;br&gt;
                        &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;&lt;br&gt;
                    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;&lt;br&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"699456451cc24f028d2aa99d7534c219"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;br&gt;
                        &lt;/span&gt;&lt;span class="nl"&gt;"tier"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Bronze"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&lt;br&gt;
                        &lt;/span&gt;&lt;span class="nl"&gt;"benefits"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;&lt;br&gt;
                            &lt;/span&gt;&lt;span class="s2"&gt;"24 hour dedicated line"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&lt;br&gt;
                            &lt;/span&gt;&lt;span class="s2"&gt;"concierge services"&lt;/span&gt;&lt;span class="w"&gt;&lt;br&gt;
                        &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;&lt;br&gt;
                        &lt;/span&gt;&lt;span class="nl"&gt;"active"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&lt;br&gt;
                        &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"699456451cc24f028d2aa99d7534c219"&lt;/span&gt;&lt;span class="w"&gt;&lt;br&gt;
                    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;br&gt;
                &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;br&gt;
            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;br&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;br&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;br&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  Youre done! Whats Next?&lt;br&gt;
&lt;/h3&gt;

&lt;p&gt;Air Pipe provides you a fast, consistent and robust development experience that you can continue extending with confidence across anything you build.&lt;/p&gt;

&lt;p&gt;You can further add testing, and schema validation for your payloads to suit your requirements or fetch additional data if required.&lt;/p&gt;

&lt;p&gt;See:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://docs.airpipe.io/docs/tutorial/validating-data" rel="noopener noreferrer"&gt;Validating Data&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://docs.airpipe.io/docs/examples/mongodb/#mongodb-find-example" rel="noopener noreferrer"&gt;MongoDB Examples&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://docs.airpipe.io/docs/tutorial/nosql-database-to-api" rel="noopener noreferrer"&gt;NoSQL Tutorial&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Performance and Reliability Built on Rust
&lt;/h3&gt;

&lt;p&gt;One of the core strengths of Air Pipe is that it is built on &lt;strong&gt;Rust&lt;/strong&gt; , known for its &lt;strong&gt;memory safety&lt;/strong&gt; , &lt;strong&gt;thread safety&lt;/strong&gt; , and &lt;strong&gt;high performance&lt;/strong&gt;. Whether you're managing large-scale databases or smaller applications, Rust ensures your APIs run smoothly and securely.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why MongoDB?
&lt;/h3&gt;

&lt;p&gt;MongoDB is a popular choice for developers due to its flexibility, scalability, and ease of use. Whether you're building a startup or an enterprise-grade application, MongoDB allows you to store and retrieve your data seamlessly. Coupled with Air Pipe's rapid deployment capabilities, it becomes easier than ever to launch and manage data-driven applications.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;Building MongoDB APIs doesn't have to be a daunting task. With Air Pipe, you can achieve in 30 seconds what traditionally takes hours or even days. The combination of performance, security, and flexibility makes Air Pipe an ideal solution for developers and businesses seeking to streamline their workflows and get to market faster.&lt;/p&gt;

&lt;p&gt;Ready to get started? Head over to &lt;a href="https://airpipe.io/" rel="noopener noreferrer"&gt;Air Pipe&lt;/a&gt; and build your first MongoDB API in just 30 seconds!&lt;/p&gt;

&lt;p&gt;💡 Visit &lt;a href="https://airpipe.io" rel="noopener noreferrer"&gt;Air Pipe&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;💡 Find us on &lt;a href="https://discord.com/invite/b8mFtjWXZj" rel="noopener noreferrer"&gt;Discord&lt;/a&gt;&lt;/p&gt;

</description>
      <category>mongodb</category>
      <category>api</category>
      <category>webdev</category>
      <category>backend</category>
    </item>
  </channel>
</rss>
