<?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: Alterpro</title>
    <description>The latest articles on DEV Community by Alterpro (@alterpro).</description>
    <link>https://dev.to/alterpro</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4040201%2F1b889a5a-d62c-4c5c-b18e-9f7e9689a99f.png</url>
      <title>DEV Community: Alterpro</title>
      <link>https://dev.to/alterpro</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/alterpro"/>
    <language>en</language>
    <item>
      <title>What I learned building one API in front of 70+ AI models (LLM, image, video, music)</title>
      <dc:creator>Alterpro</dc:creator>
      <pubDate>Tue, 21 Jul 2026 14:14:47 +0000</pubDate>
      <link>https://dev.to/alterpro/what-i-learned-building-one-api-in-front-of-70-ai-models-llm-image-video-music-54e1</link>
      <guid>https://dev.to/alterpro/what-i-learned-building-one-api-in-front-of-70-ai-models-llm-image-video-music-54e1</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Disclosure up front: I work on &lt;strong&gt;&lt;a href="https://you.bot" rel="noopener noreferrer"&gt;you.bot&lt;/a&gt;&lt;/strong&gt;, the gateway described here. This is a build-log about the engineering problems, not a sales pitch — the patterns apply whether you build your own gateway or use one.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The problem that started it
&lt;/h2&gt;

&lt;p&gt;Every AI feature I shipped came with its own tax. The LLM had one SDK and one dashboard. The image model had another. Video was a third provider with a totally different async flow. Music was a fourth. Four SDKs, four invoices, four sets of retry logic, four ways to handle failures.&lt;/p&gt;

&lt;p&gt;The moment I wanted to A/B two image models, or swap an LLM for a cheaper one, I was rewriting integration code and reconciling another bill. So we built a single endpoint that fronts 70+ models across text, image, video and music, where switching models is a one-line change. Here's what turned out to be hard.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Every provider has a different idea of "a request"
&lt;/h2&gt;

&lt;p&gt;LLMs are mostly request/response. Image is sometimes sync, sometimes not. Video is almost always a long-running job — you submit, you poll or wait for a webhook, and it can take minutes. Music (like &lt;a href="https://you.bot/models/suno-v4-5" rel="noopener noreferrer"&gt;Suno&lt;/a&gt;) is the same: you don't get a song back on the open socket.&lt;/p&gt;

&lt;p&gt;If you expose these differences to the caller, you've moved the complexity onto them. The decision that mattered most: &lt;strong&gt;normalize everything to one create-then-poll task shape.&lt;/strong&gt; You create a task, then poll it until it succeeds (text models return their result inline in the create response, so you can skip the poll):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;POST https://you.bot/api/v1/generate
Authorization: Bearer YOUR_API_KEY

{ "modelId": "suno-v4-5",
  "input": { "prompt": "lofi beat, rainy night", "title": "Rainy Lofi", "style": "lofi, chill" } }

→ { "taskId": "t_abc", "creditsCharged": 13 }
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET https://you.bot/api/v1/task/t_abc?model=suno-v4-5

→ { "state": "success", "resultUrls": ["https://…"] }
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same two-call contract for a fast LLM call and a three-minute video render. Callers can poll, or pass a &lt;code&gt;callbackUrl&lt;/code&gt; and get a signed webhook on completion. Switching from music to video (&lt;a href="https://you.bot/models/kling-3-0" rel="noopener noreferrer"&gt;Kling&lt;/a&gt;) or image (&lt;a href="https://you.bot/models/gpt-image-2-text-to-image" rel="noopener noreferrer"&gt;GPT Image 2&lt;/a&gt;) is just a different &lt;code&gt;modelId&lt;/code&gt; and a different &lt;code&gt;input&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. The modelId is the only thing that should change
&lt;/h2&gt;

&lt;p&gt;The whole point is that trying a new model shouldn't be a project. The surface a caller touches is deliberately tiny: one endpoint, one &lt;code&gt;Authorization&lt;/code&gt; header, and a &lt;code&gt;modelId&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;inp&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://you.bot/api/v1/generate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Authorization&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bearer &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;API_KEY&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;modelId&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;model_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;input&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;inp&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;   &lt;span class="c1"&gt;# -&amp;gt; {"taskId": "...", "creditsCharged": ...}
&lt;/span&gt;
&lt;span class="c1"&gt;# swap models by changing one field
# (input fields vary per model — each model page documents its own)
&lt;/span&gt;&lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gemini-3-1-pro&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prompt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;nano-banana-pro&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prompt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="nf"&gt;generate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kling-3-0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;       &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prompt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every model has a page with its own inputs, price and an in-browser playground (e.g. &lt;a href="https://you.bot/models/gemini-3-1-pro" rel="noopener noreferrer"&gt;Gemini 3.1 Pro&lt;/a&gt;, &lt;a href="https://you.bot/models/nano-banana-pro" rel="noopener noreferrer"&gt;Nano Banana Pro&lt;/a&gt;) so you can test with your own prompt before writing a line of integration code. That "try before you wire it up" step removed most of our support questions.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Only charging for successful output changes your whole billing model
&lt;/h2&gt;

&lt;p&gt;If a generation fails, errors, or comes back empty, the user should not pay for it. Sounds obvious; it's annoying to implement because "success" is defined differently per provider, and refunds have to be idempotent so a retried webhook doesn't double-credit.&lt;/p&gt;

&lt;p&gt;We ended up with a per-model success predicate and a ledger where every debit can be reversed by task id. Credits come from one prepaid wallet shared across all models (the create response tells you &lt;code&gt;creditsCharged&lt;/code&gt; per call), so there's no per-provider balance to juggle — and because you only pay on success, the effective price drops below the sticker price.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Reliability: keep the caller's existing key as a fallback
&lt;/h2&gt;

&lt;p&gt;The honest objection to any gateway is "now you're a single point of failure between me and the model." The pattern that made people comfortable: &lt;strong&gt;let the app keep its existing provider key as a fallback route.&lt;/strong&gt; Primary traffic goes through the gateway; if a call fails, it falls back to the provider directly. Multi-provider routing targets high uptime, but the caller never bets reliability on us alone.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Make the docs machine-readable
&lt;/h2&gt;

&lt;p&gt;A lot of "how do I call X model" questions now start in ChatGPT or Perplexity, not Google. So every model page also ships a plain-markdown version (append &lt;code&gt;/md&lt;/code&gt; to the URL, e.g. &lt;code&gt;/models/suno-v4-5/md&lt;/code&gt;) that an LLM can read cleanly. If assistants are going to answer developer questions anyway, the least you can do is give them accurate, structured source material.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd tell my past self
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Normalize to one create-then-poll task shape early. Retrofitting it later is painful.&lt;/li&gt;
&lt;li&gt;Define "success" per provider before you promise charge-on-success billing.&lt;/li&gt;
&lt;li&gt;Make refunds idempotent from day one.&lt;/li&gt;
&lt;li&gt;A playground per model is worth more than another paragraph of docs.&lt;/li&gt;
&lt;li&gt;Assume an LLM will read your docs before a human does.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The model catalog with per-model pricing and playgrounds is at &lt;a href="https://you.bot/market" rel="noopener noreferrer"&gt;you.bot/market&lt;/a&gt;. Happy to answer architecture questions in the comments — especially on fallback-routing and charge-on-success, since those drove the most debate on our side.&lt;/p&gt;

</description>
      <category>api</category>
      <category>ai</category>
      <category>webdev</category>
      <category>architecture</category>
    </item>
  </channel>
</rss>
