<?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[Pau Drudis]]></title><description><![CDATA[Pau Drudis]]></description><link>https://paudrudis.hashnode.dev</link><generator>RSS for Node</generator><lastBuildDate>Tue, 27 Oct 2026 16:00:39 GMT</lastBuildDate><atom:link href="https://paudrudis.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[How I built 54 pay-per-job agents with one Python worker and zero paid infrastructure]]></title><description><![CDATA[I put 54 small agents on the NEAR AI Agent Market (market.near.ai). Each one does a single job and returns a file. Here is how the setup works. I wanted…]]></description><link>https://paudrudis.hashnode.dev/how-i-built-54-pay-per-job-agents-with-one-python-worker-and-zero-paid-infrastructure</link><guid isPermaLink="true">https://paudrudis.hashnode.dev/how-i-built-54-pay-per-job-agents-with-one-python-worker-and-zero-paid-infrastructure</guid><dc:creator><![CDATA[Pau Drudis]]></dc:creator><pubDate>Thu, 22 Oct 2026 22:06:15 GMT</pubDate><content:encoded><![CDATA[<p>I put 54 small agents on the NEAR AI Agent Market (market.near.ai). Each one does a single job and returns a file. Here is how the setup works.</p>
<h2>The problem</h2>
<p>I wanted small, single-purpose agents that return something you can check, not a chat answer. Examples of what they produce: NEAR wallet and contract reports, curve-fit and stats reports, site health grades, CSV dashboards, and BibTeX cleanup.</p>
<h2>Design</h2>
<p><strong>One Python worker.</strong> It polls the market API for jobs instead of hosting a separate service per agent.</p>
<p><strong>Plugin backend.</strong> Each agent is a plugin that does one job.</p>
<p><strong>Deterministic outputs.</strong> The agents run plain code on public data, with no LLM in the loop, so the same input gives the same output and results are checkable.</p>
<h2>Pricing</h2>
<p>Jobs cost 0.10-0.50 USDC each, paid through market escrow.</p>
<h2>Catalog</h2>
<p>The catalog page shows a sample output for each agent: <a href="https://narwhalthepong.github.io/near-agents/">https://narwhalthepong.github.io/near-agents/</a></p>
<h2>What would you hire an agent for?</h2>
<p>I'll build what people ask for. Tell me in the comments which single-job agent you'd actually use.</p>
<p><em>Originally published on dev.to: <a href="https://dev.to/pau_drudis_6182de07fdc9f0/how-i-built-54-pay-per-job-agents-with-one-python-worker-and-zero-paid-infrastructure-1ml0">https://dev.to/pau_drudis_6182de07fdc9f0/how-i-built-54-pay-per-job-agents-with-one-python-worker-and-zero-paid-infrastructure-1ml0</a></em></p>
]]></content:encoded></item><item><title><![CDATA[Gumroad fees in 2026, explained: what you actually keep on a $5, $19 and $49 sale]]></title><description><![CDATA[Gumroad's 10% + $0.50 fee hits cheap products hardest, and Discover takes 30%. Worked examples, break-even prices and how to price around it.]]></description><link>https://paudrudis.hashnode.dev/gumroad-fees-in-2026-explained-what-you-actually-keep-on-a-5-19-and-49-sale</link><guid isPermaLink="true">https://paudrudis.hashnode.dev/gumroad-fees-in-2026-explained-what-you-actually-keep-on-a-5-19-and-49-sale</guid><category><![CDATA[gumroad]]></category><category><![CDATA[webdev]]></category><category><![CDATA[pricing]]></category><category><![CDATA[indiehackers]]></category><dc:creator><![CDATA[Pau Drudis]]></dc:creator><pubDate>Tue, 20 Oct 2026 06:05:51 GMT</pubDate><content:encoded><![CDATA[<p>Gumroad's pricing looks simple: <strong>10% + $0.50 per sale</strong> on direct sales, and <strong>30%</strong> when the sale comes through Gumroad Discover, its marketplace. Gumroad is also merchant of record, so it handles sales tax and VAT. In practice, that flat $0.50 is what decides whether a cheap product is worth selling.</p>
<p><em>Check Gumroad's pricing page before relying on these numbers, since fees change.</em></p>
<h2>What you keep</h2>
<table>
<thead>
<tr>
<th>Price</th>
<th>Direct sale (10% + $0.50)</th>
<th>You keep</th>
<th>Effective fee</th>
<th>Via Discover (30%)</th>
<th>You keep</th>
</tr>
</thead>
<tbody><tr>
<td>$1</td>
<td>$0.60</td>
<td>$0.40</td>
<td>60%</td>
<td>$0.30</td>
<td>$0.70</td>
</tr>
<tr>
<td>$5</td>
<td>$1.00</td>
<td>$4.00</td>
<td>20%</td>
<td>$1.50</td>
<td>$3.50</td>
</tr>
<tr>
<td>$9</td>
<td>$1.40</td>
<td>$7.60</td>
<td>15.6%</td>
<td>$2.70</td>
<td>$6.30</td>
</tr>
<tr>
<td>$19</td>
<td>$2.40</td>
<td>$16.60</td>
<td>12.6%</td>
<td>$5.70</td>
<td>$13.30</td>
</tr>
<tr>
<td>$49</td>
<td>$5.40</td>
<td>$43.60</td>
<td>11.0%</td>
<td>$14.70</td>
<td>$34.30</td>
</tr>
<tr>
<td>$99</td>
<td>$10.40</td>
<td>$88.60</td>
<td>10.5%</td>
<td>$29.70</td>
<td>$69.30</td>
</tr>
</tbody></table>
<p>Two things stand out:</p>
<ul>
<li><strong>Under about $5, the $0.50 dominates.</strong> A $1 product loses 60% to fees on a direct sale. That's why PWYW products with a $0 minimum or $1 tips barely move the needle.</li>
<li><strong>Discover takes a big cut, but it's still extra reach.</strong> Discover buyers are customers you wouldn't have found yourself. It's acquisition cost, not lost revenue.</li>
</ul>
<p>Payment processing is on top of this in some cases, and payout methods can carry their own small fees. Look at a real payout once to see your true net.</p>
<h2>Pricing around the fee</h2>
<ul>
<li><strong>Bundle low-priced items.</strong> Three $5 products sold separately cost $3.00 in fees. One $15 bundle costs $2.00.</li>
<li><strong>Use pay-what-you-want with a sensible minimum.</strong> A $9 minimum loses 15.6% to fees, and many buyers pay above it.</li>
<li><strong>Send your own traffic directly.</strong> Links from your blog, newsletter or social profiles are charged as direct sales at 10% + $0.50, not 30%.</li>
<li><strong>Price for the take-home amount.</strong> If you want $20 per sale, price at $22.78 or more: <code>(target + 0.50) / 0.90</code>.</li>
</ul>
<h2>Formula</h2>
<pre><code>direct_net   = price * 0.90 - 0.50
discover_net = price * 0.70
price_for_target_direct = (target + 0.50) / 0.90
</code></pre>
<h2>Tools</h2>
<ul>
<li><strong><a href="https://paudrudis.gumroad.com/l/ijrzbz">Creator Payout Calculator Pro</a></strong> runs these numbers in your browser for any price, so you can see direct and Discover payouts before you set a price.</li>
<li><strong><a href="https://paudrudis.gumroad.com/l/sudmq">Gumroad Seller Finance Tracker</a></strong> imports your sales CSV and applies the fees to every row automatically.</li>
</ul>
<p>More guides: <a href="https://narwhalthepong.github.io/gumroad-license-check/">Gumroad developer guides</a>. Not affiliated with Gumroad.</p>
]]></content:encoded></item><item><title><![CDATA[Your Gumroad license check probably lets refunded buyers in (and how to fix it)]]></title><description><![CDATA[If you sell software on Gumroad and gate it with license keys, there's a good chance your check looks like this: The problem: success only tells you the…]]></description><link>https://paudrudis.hashnode.dev/your-gumroad-license-check-probably-lets-refunded-buyers-in-and-how-to-fix-it</link><guid isPermaLink="true">https://paudrudis.hashnode.dev/your-gumroad-license-check-probably-lets-refunded-buyers-in-and-how-to-fix-it</guid><category><![CDATA[gumroad]]></category><category><![CDATA[JavaScript]]></category><category><![CDATA[Python]]></category><category><![CDATA[webdev]]></category><dc:creator><![CDATA[Pau Drudis]]></dc:creator><pubDate>Tue, 20 Oct 2026 03:53:21 GMT</pubDate><content:encoded><![CDATA[<p>If you sell software on Gumroad and gate it with license keys, there's a good chance your check looks like this:</p>
<pre><code class="language-js">const res = await fetch("https://api.gumroad.com/v2/licenses/verify", {
  method: "POST",
  body: new URLSearchParams({ product_id, license_key }),
}).then((r) =&gt; r.json());

if (res.success) unlock(); // looks fine...
</code></pre>
<p>The problem: <strong><code>success</code> only tells you the key exists.</strong> After a buyer is refunded, files a chargeback, or cancels a subscription, <code>/v2/licenses/verify</code> still returns <code>success: true</code>. The status lives in the <code>purchase</code> object, so a check on <code>success</code> alone lets refunded buyers keep using your app.</p>
<h2>The fix</h2>
<p>Check the purchase flags too:</p>
<pre><code class="language-js">function evaluate(res) {
  if (!res?.success) return { valid: false, reason: res?.message ?? "invalid license" };
  const p = res.purchase ?? {};
  if (p.refunded) return { valid: false, reason: "purchase refunded" };
  if (p.chargebacked) return { valid: false, reason: "purchase chargebacked" };
  if (p.disputed &amp;&amp; !p.dispute_won) return { valid: false, reason: "purchase disputed" };
  if (p.subscription_ended_at) return { valid: false, reason: "subscription ended" };
  if (p.subscription_failed_at) return { valid: false, reason: "subscription payment failed" };
  return { valid: true, reason: "ok" };
}
</code></pre>
<p>Python:</p>
<pre><code class="language-python">def evaluate(res):
    if not res or not res.get("success"):
        return {"valid": False, "reason": (res or {}).get("message", "invalid license")}
    p = res.get("purchase") or {}
    for flag in ("refunded", "chargebacked"):
        if p.get(flag):
            return {"valid": False, "reason": f"purchase {flag}"}
    if p.get("disputed") and not p.get("dispute_won"):
        return {"valid": False, "reason": "purchase disputed"}
    if p.get("subscription_ended_at") or p.get("subscription_failed_at"):
        return {"valid": False, "reason": "subscription inactive"}
    return {"valid": True, "reason": "ok"}
</code></pre>
<h2>Two more tips</h2>
<ol>
<li><strong>Send <code>increment_uses_count=false</code> on re-checks.</strong> By default every verify call bumps the key's <code>uses</code> count, so if you count activations, routine re-checks will burn through them. Only increment on the first activation.</li>
<li><strong>Re-check now and then, not just once.</strong> Refunds happen after activation. A re-check every few days catches them. If you do this, cache the last good result so your app doesn't break offline.</li>
</ol>
<h2>Free code</h2>
<p>I put both versions (zero dependencies, with tests) in a small MIT repo: <strong><a href="https://github.com/NarwhalThePong/gumroad-license-check">https://github.com/NarwhalThePong/gumroad-license-check</a></strong></p>
<p>If you want the production version, with a signed offline cache and grace period, activation limits, and a small proxy server so your product ID and logic stay off the client, I sell it as the <strong><a href="https://paudrudis.gumroad.com/l/dufcs">Gumroad License Kit</a></strong>.</p>
<p>Have you hit other Gumroad API gotchas? Tell me in the comments.</p>
]]></content:encoded></item></channel></rss>