<?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: Dato Daraselia</title>
    <description>The latest articles on DEV Community by Dato Daraselia (@ddaras).</description>
    <link>https://dev.to/ddaras</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%2F1090757%2F5e219885-493f-4075-b89a-66aba9d4e43b.jpeg</url>
      <title>DEV Community: Dato Daraselia</title>
      <link>https://dev.to/ddaras</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ddaras"/>
    <language>en</language>
    <item>
      <title>Stop sharing your OpenAI API key with your team</title>
      <dc:creator>Dato Daraselia</dc:creator>
      <pubDate>Mon, 19 Oct 2026 19:30:47 +0000</pubDate>
      <link>https://dev.to/ddaras/stop-sharing-your-openai-api-key-with-your-team-1fek</link>
      <guid>https://dev.to/ddaras/stop-sharing-your-openai-api-key-with-your-team-1fek</guid>
      <description>&lt;p&gt;You built an agent on the OpenAI Agents API, or on Claude Managed Agents. It works. You show it to the support lead and they say: &lt;em&gt;"Can my team use this tomorrow?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The fastest answer is to paste the API key into Slack along with a script, and let everyone run it.&lt;/p&gt;

&lt;p&gt;Please don't. Here's why, and what I learned building the alternative.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a shared key goes wrong
&lt;/h2&gt;

&lt;p&gt;A shared API key isn't access to &lt;em&gt;one agent&lt;/em&gt;. It's access to your account.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It often reaches more than one agent.&lt;/strong&gt; Depending on how the key is scoped, it can reach other agents, other models and other projects. You meant to share one thing and handed over many.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You can't revoke it for one person.&lt;/strong&gt; Someone leaves the team? Rotate the key, and now everyone's setup breaks at the same time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It spreads.&lt;/strong&gt; It ends up in Slack history, &lt;code&gt;.env&lt;/code&gt; files, screenshots and someone's personal laptop. Each copy is a liability.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No per-person limits.&lt;/strong&gt; One runaway loop on someone's machine, and the whole bill lands on you.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No privacy between teammates.&lt;/strong&gt; Everyone shares one identity, so there's no "my chats" versus "your chats".&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;What you actually want is to share &lt;strong&gt;the agent&lt;/strong&gt;, not &lt;strong&gt;the key&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "sharing an agent" really needs
&lt;/h2&gt;

&lt;p&gt;When I started, I thought it was a text box and a streaming response. It turned out to be eight things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Sign-in&lt;/strong&gt; for people who don't have platform accounts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Membership per agent&lt;/strong&gt;: who can use it, who can invite, who owns the key.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A server-side proxy&lt;/strong&gt;, so the key never reaches a browser.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Streaming&lt;/strong&gt;: text, plus tool activity, so people can see what the agent is doing.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Chat history per person.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Private and shared chats&lt;/strong&gt;, with clear rules about who can see, unshare and delete them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Server-enforced limits.&lt;/strong&gt; Limits that exist only in the UI aren't limits.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Platform quirks&lt;/strong&gt;: environments and vaults on Claude Managed Agents, the hosted sandbox and Vault on OpenAI, and tool calls that pause waiting for approval.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;None of these is hard on its own. Together they're a product.&lt;/p&gt;

&lt;h2&gt;
  
  
  The core pattern: the key lives on the server, the user only sees the agent
&lt;/h2&gt;

&lt;p&gt;The heart of it is a small proxy. The browser talks only to your API. Your API checks who's calling, then calls the platform with the key:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Pseudocode: the shape of every chat request&lt;/span&gt;
&lt;span class="nx"&gt;app&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/v1/chat&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;requireSignedInUser&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;agentId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// 1. Membership: return 404, not 403, so non-members can't probe which agents exist&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isMember&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// 2. Limits, enforced here, not in the UI&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;overDailyLimit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sendStatus&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;429&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// 3. The key comes from server-side storage and never leaves this process&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;apiKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;secrets&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// 4. Call the platform and stream a normalized event format back to the browser&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;events&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;platformFor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;sessionId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sessionId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nf"&gt;streamAsSSE&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;events&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&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;A few rules I'd now treat as non-negotiable:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The key is never in a response body&lt;/strong&gt;: not in JSON, not in an SSE event, not in an error message.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep the key out of the database the client can read.&lt;/strong&gt; If your frontend reads documents directly (Firebase and friends), store the key somewhere the browser cannot reach at all, and deny access to it in the security rules too.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One key per agent, held by its owner.&lt;/strong&gt; The owner pays, the owner rotates it, and nobody else ever holds it.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The surprise: the platforms don't know &lt;em&gt;who&lt;/em&gt; is chatting
&lt;/h2&gt;

&lt;p&gt;This one took me a while to notice. Hosted agent platforms run sessions for &lt;strong&gt;your API key&lt;/strong&gt;, not for your users. They don't track which person started which session.&lt;/p&gt;

&lt;p&gt;So if you want "my chats are private", you need your own small &lt;strong&gt;access index&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// One row per session. This is not the conversation; the messages stay on the platform.&lt;/span&gt;
&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;SessionAccess&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;sessionId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// the platform's session id&lt;/span&gt;
  &lt;span class="nl"&gt;ownerId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// your user id&lt;/span&gt;
  &lt;span class="nl"&gt;shared&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// visible to everyone on the agent?&lt;/span&gt;
  &lt;span class="nl"&gt;createdAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&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;The sidebar lists sessions you own plus the shared ones. Any request for a session you can't see returns 404 &lt;strong&gt;before&lt;/strong&gt; calling the platform. That's also why I don't store messages at all: the platform already does, and keeping a second copy is just more data to protect.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tool approvals will hang your UI
&lt;/h2&gt;

&lt;p&gt;Both platforms support tools, and some setups pause a tool call until someone approves it. If your chat UI has no approval flow, that session just waits forever.&lt;/p&gt;

&lt;p&gt;My fix: don't build an approval UI. Deny calls that are waiting for approval, with a clear message pointing at the agent's permission policy. Configure permissions where the agent lives, on the platform, and keep the chat UI simple.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build it, or don't
&lt;/h2&gt;

&lt;p&gt;If the chat is part of a &lt;strong&gt;customer-facing product&lt;/strong&gt;, build it. You'll want your own auth, branding and billing anyway.&lt;/p&gt;

&lt;p&gt;If the users are &lt;strong&gt;your own team&lt;/strong&gt;, think twice. Everything above is maintenance you'll carry as both platforms change.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclosure: I went through all of this and turned it into &lt;a href="https://melony.dev" rel="noopener noreferrer"&gt;Melony&lt;/a&gt;, a hosted team chat for agents built on the OpenAI Agents API or Claude Managed Agents. You connect the agent with its key once, invite teammates by email, and the key stays on the server. It's free, and the &lt;a href="https://melony.dev/security/" rel="noopener noreferrer"&gt;security page&lt;/a&gt; lists exactly what it stores. But the pattern above works whether you use it or build your own.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;How are you sharing agents with your team right now?&lt;/strong&gt; A shared key, an internal tool, or something else? I'd like to hear what's working for you.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>openai</category>
      <category>webdev</category>
      <category>claude</category>
    </item>
    <item>
      <title>Solving AI fragmentation problem via Slack-like platform</title>
      <dc:creator>Dato Daraselia</dc:creator>
      <pubDate>Tue, 07 Apr 2026 18:51:34 +0000</pubDate>
      <link>https://dev.to/ddaras/solving-ai-fragmentation-problem-via-slack-like-platform-25kn</link>
      <guid>https://dev.to/ddaras/solving-ai-fragmentation-problem-via-slack-like-platform-25kn</guid>
      <description>&lt;p&gt;We believe that 2026 must be the year of coordination and orchestration of AI agents so we invest our spare time to build something really amazing!&lt;/p&gt;

&lt;p&gt;Would love to hear your feedback! &lt;/p&gt;

&lt;p&gt;Here is a (temp) source: &lt;a href="https://github.com/meetopenbot/openbot/tree/refactor/slack" rel="noopener noreferrer"&gt;https://github.com/meetopenbot/openbot/tree/refactor/slack&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>typescript</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
