<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Programmable Blog]]></title><description><![CDATA[Programmable Blog]]></description><link>https://programmable.hashnode.dev</link><image><url>https://cdn.hashnode.com/uploads/logos/6aecbbf8dd02d744daa2a177/e006c641-f3be-49ca-aca1-5d9021df70ee.png</url><title>Programmable Blog</title><link>https://programmable.hashnode.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Sat, 07 Nov 2026 12:21:14 GMT</lastBuildDate><atom:link href="https://programmable.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Run an AI agent as one HTTP step from Zapier, n8n or a script]]></title><description><![CDATA[I build Programmable, a tool that turns one sentence into a sales agent. A question I kept getting was how to run that agent from somewhere else: a Zap, an…]]></description><link>https://programmable.hashnode.dev/run-an-ai-agent-as-one-http-step-from-zapier-n8n-or-a-script</link><guid isPermaLink="true">https://programmable.hashnode.dev/run-an-ai-agent-as-one-http-step-from-zapier-n8n-or-a-script</guid><category><![CDATA[Zapier]]></category><category><![CDATA[n8n]]></category><category><![CDATA[ai agents]]></category><category><![CDATA[api]]></category><category><![CDATA[automation]]></category><dc:creator><![CDATA[Programmable]]></dc:creator><pubDate>Thu, 05 Nov 2026 17:34:50 GMT</pubDate><content:encoded><![CDATA[<p>I build Programmable, a tool that turns one sentence into a sales agent. A question I kept getting was how to run that agent from somewhere else: a Zap, an n8n flow, a cron job. The answer ended up being boring on purpose. Every agent is one HTTP step.</p>
<p>Here is how that works, and the two problems it had to solve along the way.</p>
<h2>The call</h2>
<p>An agent has an address. You give it the company from whatever step came before and get people, checked emails and research back:</p>
<pre><code class="language-http">GET https://programmable.info/x/live?request=Find the head of sales and write an opener about their hiring&amp;format=json&amp;domains=acme.com
Authorization: Bearer YOUR_API_KEY
</code></pre>
<p>The first time a sentence runs, it's read once into a plan. Every call after that runs the stored plan, so the same sentence doesn't get re-interpreted differently on Tuesday than it did on Monday.</p>
<p>The response is one row per person (name, title, checked email, the opener) plus a summary with the credits used.</p>
<h2>In Zapier, n8n and Make</h2>
<p>It's the same request in each tool:</p>
<ol>
<li>Start with a trigger that has the company in it: a new row in Google Sheets, a new company in HubSpot, a form answer.</li>
<li>Add an HTTP step (in Zapier, Webhooks by Zapier with event GET) pointing at the agent's link.</li>
<li>Map the company's website into <code>domains</code>, and add the <code>Authorization: Bearer</code> header.</li>
</ol>
<p>One gotcha: Webhooks by Zapier is a Premium app, so it needs a paid Zapier plan. n8n and Google Sheets don't have that limit.</p>
<h2>Problem one: Zapier waits about 30 seconds</h2>
<p>A quick lookup finishes well inside that. A longer run (finding new companies, AI research on each one) doesn't. If the step just hung, Zapier would give up on it while the work was still running.</p>
<p>So the agent's link always answers within 25 seconds. If the results are ready, you get them. If not, you get this, and the run carries on:</p>
<pre><code class="language-json">{ "status": "started", "run_id": "...", "status_url": "..." }
</code></pre>
<p>The <code>status_url</code> shows progress, and once the run is done, the summary and rows.</p>
<h2>Problem two: getting rows from a long run back into Zapier</h2>
<p>Polling from inside a Zap is awkward. The cleaner pattern is a second zap:</p>
<ol>
<li>Make a zap that starts with <strong>Webhooks by Zapier, Catch Hook</strong>, and copy its URL.</li>
<li>Paste it as your webhook in the app (Developers, Webhooks).</li>
<li>Rows arrive at the Catch Hook in batches as they finish (event <code>sheet.rows</code>), then a summary (<code>sheet.completed</code>).</li>
</ol>
<p>Every batch is signed with <code>X-Programmable-Signature</code> (HMAC-SHA256) and carries an <code>X-Programmable-Id</code> that stays the same across retries, so you can drop duplicates with one filter step.</p>
<h2>What a link can and can't change</h2>
<p>A deliberate limit: the link only takes the trigger's details. That means <code>company</code> or <code>domains</code>, or a person (name plus company, or a LinkedIn URL), a lower <code>limit</code>, a <code>campaign_name</code>, and <code>format=json</code>. It can't change the columns, the AI questions, where results are sent or whether sends need approval. Those stay with the agent. A link pasted into a public Zap can't be made to spend more or send somewhere new.</p>
<h2>The rest of the surface</h2>
<ul>
<li><code>POST https://programmable.info/api/sheet</code> for the full API, with CSV by default, JSON with <code>format=json</code>, or NDJSON events with <code>Accept: application/x-ndjson</code>.</li>
<li>An MCP server at <code>https://programmable.info/api/mcp</code> for Claude, Cursor and other assistants, with the same key and limits.</li>
<li>Plain errors: 401 unknown key, 402 out of credits, 428 a sentence not yet confirmed, 429 over the hourly limit.</li>
</ul>
<p>New accounts start with 500 credits included, no card. Full reference: <a href="https://programmable.info/developers">https://programmable.info/developers</a></p>
<p>If you wire this into something I haven't thought of, I'd like to see it.</p>
]]></content:encoded></item></channel></rss>