<?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: tutorial</title>
    <description>The latest articles tagged 'tutorial' on DEV Community.</description>
    <link>https://dev.to/t/tutorial</link>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tag/tutorial"/>
    <language>en</language>
    <item>
      <title>How to Choose the Right AI Tutor for Exam Preparation</title>
      <dc:creator>Rahul Jaiswal</dc:creator>
      <pubDate>Wed, 22 Apr 2026 06:47:01 +0000</pubDate>
      <link>https://dev.to/jaiswal007/how-to-choose-the-right-ai-tutor-for-exam-preparation-3h1i</link>
      <guid>https://dev.to/jaiswal007/how-to-choose-the-right-ai-tutor-for-exam-preparation-3h1i</guid>
      <description>&lt;h1&gt;
  
  
  How to Choose the Right AI Tutor for Your Exam Preparation
&lt;/h1&gt;

&lt;p&gt;With dozens of AI tutor apps available in India, choosing the right one matters. Here is what to look for.&lt;/p&gt;

&lt;h2&gt;
  
  
  5 Things That Matter in an AI Tutor
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Camera-Based Input
&lt;/h3&gt;

&lt;p&gt;If you study from physical textbooks (most Indian students do), your AI tutor must support camera input. &lt;a href="https://easelearn.ai/doubt-solver" rel="noopener noreferrer"&gt;EaseLearn AI&lt;/a&gt; reads handwritten and printed questions. ChatGPT requires typing everything.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Indian Curriculum Awareness
&lt;/h3&gt;

&lt;p&gt;A good &lt;a href="https://easelearn.ai/ai-tutor" rel="noopener noreferrer"&gt;AI tutor&lt;/a&gt; knows the difference between CBSE Class 12 Physics and JEE Advanced Physics. Generic AI tools give generic answers. EaseLearn adapts to your specific board and exam.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Step-by-Step Solutions
&lt;/h3&gt;

&lt;p&gt;Not just answers — you need the working. The &lt;a href="https://easelearn.ai/math-solver" rel="noopener noreferrer"&gt;math solver&lt;/a&gt; should show every step with reasoning. This is how you learn the method, not just copy the result.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Visual Learning
&lt;/h3&gt;

&lt;p&gt;For topics like organic chemistry or electromagnetic induction, text is not enough. &lt;a href="https://easelearn.ai/immersive-classroom" rel="noopener noreferrer"&gt;Immersive Classroom&lt;/a&gt; generates live slides with diagrams — patent-pending technology.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Affordable Pricing
&lt;/h3&gt;

&lt;p&gt;An AI tutor should cost less than a single tuition session. &lt;a href="https://easelearn.ai/pricing" rel="noopener noreferrer"&gt;EaseLearn AI pricing&lt;/a&gt;: free tier available, Premium Rs 199/month.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Best AI Tutor for Indian Students
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://easelearn.ai/ai-tutor" rel="noopener noreferrer"&gt;EaseLearn AI&lt;/a&gt; checks all 5 boxes. 100,000+ students. Featured in &lt;a href="https://aninews.in/news/business/indias-first-ai-classroom-that-refuses-to-move-on-until-you-understand-and-its-free20260420160950/" rel="noopener noreferrer"&gt;ANI News&lt;/a&gt; and &lt;a href="https://www.tribuneindia.com/news/business/indias-first-ai-classroom-that-refuses-to-move-on-until-you-understand-and-its-free/" rel="noopener noreferrer"&gt;Tribune India&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://easelearn.ai/ai-tutor" rel="noopener noreferrer"&gt;Try free&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;&lt;a href="https://easelearn.ai" rel="noopener noreferrer"&gt;EaseLearn AI&lt;/a&gt; — Free AI tutor. &lt;a href="https://easelearn.ai/doubt-solver" rel="noopener noreferrer"&gt;Doubt solver&lt;/a&gt; . &lt;a href="https://easelearn.ai/math-solver" rel="noopener noreferrer"&gt;Math solver&lt;/a&gt; . &lt;a href="https://easelearn.ai/immersive-classroom" rel="noopener noreferrer"&gt;Immersive Classroom&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>education</category>
      <category>india</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>SEO vs GEO: Which One Actually Grows Your Traffic?</title>
      <dc:creator>yashu</dc:creator>
      <pubDate>Wed, 22 Apr 2026 06:15:46 +0000</pubDate>
      <link>https://dev.to/yashu26/seo-vs-geo-which-one-actually-grows-your-traffic-3j3</link>
      <guid>https://dev.to/yashu26/seo-vs-geo-which-one-actually-grows-your-traffic-3j3</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.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%2Fh6saw3c6neblh1zb8s2p.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fh6saw3c6neblh1zb8s2p.jpg" alt=" " width="800" height="480"&gt;&lt;/a&gt;&lt;br&gt;
In today’s fast-changing digital world, businesses are always looking for better ways to grow online. One of the biggest debates right now is SEO vs GEO. While both strategies aim to increase visibility and traffic, they work in very different ways. Understanding the difference can help you choose the right approach for your goals.&lt;br&gt;
What is SEO?&lt;br&gt;
Search Engine Optimization (SEO) is the process of improving your website so it ranks higher on search engines like Google. It focuses on keywords, backlinks, content quality, and technical performance. When people search for something, SEO helps your website appear in the results.&lt;br&gt;
SEO has been around for years, and it still plays a major role in driving organic traffic. Businesses use SEO to create blog posts, optimize pages, and target search queries that users type every day.&lt;br&gt;
What is GEO?&lt;br&gt;
Generative Engine Optimization (GEO) is a newer concept. It focuses on optimizing content for AI-powered tools and search experiences. Instead of just ranking on search engines, GEO helps your content appear in AI overviews, chat-based results, and voice assistants.&lt;br&gt;
With the rise of AI, users are no longer just clicking links—they are getting direct answers. This is where GEO becomes important. It helps improve AI brand visibility by making sure your content is recognized and used by AI systems.&lt;br&gt;
SEO vs GEO: Key Differences&lt;br&gt;
When comparing SEO vs GEO, the biggest difference lies in how users find information. SEO targets traditional search results, while GEO focuses on AI-generated answers.&lt;br&gt;
SEO relies heavily on keywords and backlinks. GEO, on the other hand, prioritizes context, authority, and structured information. For example, if your content clearly answers a question, it has a better chance of being featured in AI overviews.&lt;br&gt;
Another difference is user behavior. In SEO, users click on links. In GEO, users may get answers directly without visiting your site. This means your content needs to be clear, trustworthy, and well-structured.&lt;br&gt;
Which One Drives More Traffic?&lt;br&gt;
The answer isn’t simple. In the debate of SEO vs GEO, both strategies are important.&lt;br&gt;
SEO still brings consistent traffic, especially for blogs and websites targeting search queries. It is reliable and measurable. However, GEO is growing fast as more users rely on AI tools for quick answers.&lt;br&gt;
If your goal is long-term growth, combining both strategies is the best approach. SEO helps you rank, while GEO improves your chances of appearing in AI overviews and boosting AI brand visibility.&lt;br&gt;
How to Use SEO and GEO Together&lt;br&gt;
To win the SEO vs GEO game, you need a balanced strategy:&lt;br&gt;
Use clear and simple language in your content&lt;br&gt;
Answer questions directly to improve chances of appearing in AI overviews&lt;br&gt;
Optimize keywords naturally (like “SEO vs GEO”)&lt;br&gt;
Build authority with high-quality, helpful content&lt;br&gt;
Structure your content with headings and short paragraphs&lt;br&gt;
By doing this, you can benefit from both traditional search traffic and AI-driven visibility.&lt;br&gt;
Final Thoughts&lt;br&gt;
The future of digital marketing is not about choosing between SEO vs GEO. It’s about using both together. SEO gives you a strong foundation, while GEO prepares you for the growing world of AI search.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>productivity</category>
      <category>programming</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Serverless vs Containers: The Decision Framework That Saves CTOs From $10K/Month Mistakes</title>
      <dc:creator>Yash Pritwani</dc:creator>
      <pubDate>Wed, 22 Apr 2026 06:00:54 +0000</pubDate>
      <link>https://dev.to/yash_pritwani_07a77613fd6/serverless-vs-containers-the-decision-framework-that-saves-ctos-from-10kmonth-mistakes-14b6</link>
      <guid>https://dev.to/yash_pritwani_07a77613fd6/serverless-vs-containers-the-decision-framework-that-saves-ctos-from-10kmonth-mistakes-14b6</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://www.techsaas.cloud/blog/serverless-vs-containers-decision-framework" rel="noopener noreferrer"&gt;TechSaaS Cloud&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://www.techsaas.cloud/blog/serverless-vs-containers-decision-framework" rel="noopener noreferrer"&gt;TechSaaS Cloud&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  Serverless vs Containers: The Decision Framework That Saves CTOs From $10K/Month Mistakes
&lt;/h1&gt;

&lt;p&gt;The serverless vs containers debate isn't a technology debate. It's a math debate disguised as a technology debate. And most teams pick the wrong answer because they're arguing about architecture when they should be running a spreadsheet.&lt;/p&gt;

&lt;p&gt;We've migrated workloads in both directions — Lambda to containers when costs spiraled, and containers to Lambda when teams were over-provisioning. The pattern is always the same: teams pick based on hype, then discover the economics don't work for their specific workload. By then they've spent six months building on the wrong foundation.&lt;/p&gt;

&lt;p&gt;Here's the decision framework we use for every client engagement. It's not opinionated — it's mathematical.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Break-Even Point Nobody Talks About
&lt;/h2&gt;

&lt;p&gt;Lambda's pricing model is beautiful for low-traffic applications. You pay per invocation, per millisecond of compute time. When your app is idle, you pay zero. That's genuinely revolutionary.&lt;/p&gt;

&lt;p&gt;But the pricing model has a dirty secret: it doesn't scale linearly. It scales worse than linearly. As your traffic grows, Lambda becomes progressively more expensive relative to containers because:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;You pay per-request overhead.&lt;/strong&gt; Every invocation has a base cost regardless of duration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cold starts add latency AND cost.&lt;/strong&gt; Provisioned concurrency (the fix for cold starts) costs money whether invocations happen or not — eliminating the "pay only for what you use" advantage.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No resource sharing.&lt;/strong&gt; Each function invocation gets its own compute allocation. Containers share resources across requests, amortizing the overhead.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The break-even point for most workloads: approximately 30 million invocations per month at 200ms average duration. Below that, Lambda is cheaper. Above that, containers are cheaper — and the gap widens with every million additional invocations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real Numbers From Client Migrations
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Client A: Webhook Processor (Lambda Wins)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Workload:&lt;/strong&gt; Process incoming webhooks from 200+ integrations&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Traffic pattern:&lt;/strong&gt; Extremely bursty. 90% idle time. Peaks of 500 RPS during batch sends.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lambda cost:&lt;/strong&gt; $180/month&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Equivalent container cost:&lt;/strong&gt; ~$400/month (need enough instances to handle peaks, paying for idle time)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Decision:&lt;/strong&gt; Stay on Lambda. The bursty traffic pattern is exactly what serverless is designed for.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Client B: REST API (Containers Win by 6x)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Workload:&lt;/strong&gt; Customer-facing REST API, steady traffic&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Traffic pattern:&lt;/strong&gt; 2.3 million requests per day, consistent throughout business hours&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lambda cost:&lt;/strong&gt; $8,200/month (with provisioned concurrency for acceptable latency)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fargate cost:&lt;/strong&gt; $1,400/month (2 services, auto-scaling 2-8 tasks)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Decision:&lt;/strong&gt; Migrated to Fargate. Steady traffic means Lambda's per-request pricing works against you. The provisioned concurrency bill alone was $4,000.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Client C: ML Inference (Containers Win by 7x)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Workload:&lt;/strong&gt; Document classification pipeline, medium-sized models&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Traffic pattern:&lt;/strong&gt; 50K requests/day, models need warm loading&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lambda cost:&lt;/strong&gt; $14,000/month (hitting timeout limits, cold starts loading models)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Self-hosted GPU containers:&lt;/strong&gt; $2,100/month (leased A10, models stay warm)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Decision:&lt;/strong&gt; Migrated to self-hosted containers. Lambda's 15-minute timeout and cold start penalty for large memory functions made it technically wrong AND economically wrong.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Decision Matrix
&lt;/h2&gt;

&lt;p&gt;Use this framework for any new workload:&lt;/p&gt;

&lt;h3&gt;
  
  
  Choose Serverless When:
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Traffic is unpredictable or bursty.&lt;/strong&gt; If your service goes from 0 to 10,000 RPS and back to 0 within minutes, serverless handles this automatically. Containers require over-provisioning for peaks, wasting money during valleys.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Functions are short-lived.&lt;/strong&gt; Under 30 seconds execution time. Ideally under 5 seconds. If your workload consistently runs longer, you're fighting Lambda's pricing model.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The team is small and ops-averse.&lt;/strong&gt; No patching, no scaling decisions, no capacity planning. For a team of 3 engineers shipping an MVP, the ops overhead of containers isn't worth it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Workloads are event-driven.&lt;/strong&gt; S3 triggers, SQS processing, cron jobs that run once per hour, webhook handlers. These are Lambda's sweet spot — truly pay-per-use.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You're prototyping.&lt;/strong&gt; Need to validate an idea in a week? Lambda gets you from code to production with zero infrastructure decisions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Choose Containers When:
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Traffic is sustained and predictable.&lt;/strong&gt; More than 1 million requests per day with a consistent pattern. The per-request overhead of Lambda adds up fast.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You need persistent connections.&lt;/strong&gt; WebSockets, gRPC streams, long-polling, SSE. Lambda's request-response model doesn't support these patterns.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cold start latency is unacceptable.&lt;/strong&gt; Even with provisioned concurrency, Lambda cold starts add 100-500ms for basic functions and 1-5 seconds for functions with large dependencies. Containers start once and serve thousands of requests.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You need local state or caching.&lt;/strong&gt; In-memory caches, connection pools, loaded ML models. Lambda functions are stateless by design — every optimization that relies on state breaks the model.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Your monthly serverless bill exceeds $3,000.&lt;/strong&gt; This is the inflection point where the math almost always favors containers. Run the actual comparison.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Hybrid Approach (What We Recommend)
&lt;/h3&gt;

&lt;p&gt;Most production systems shouldn't be 100% either. The winning pattern:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Containers&lt;/strong&gt; for your core services: APIs, web servers, databases, queues, anything with sustained traffic&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Serverless&lt;/strong&gt; for glue code: event processing, file transformations, scheduled jobs, webhooks, async background tasks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This hybrid approach typically costs 60-70% less than going all-in on either strategy.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Migration Playbook
&lt;/h2&gt;

&lt;p&gt;If you've determined you're on the wrong architecture, here's the migration path:&lt;/p&gt;

&lt;h3&gt;
  
  
  Lambda → Containers:
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Identify the expensive functions.&lt;/strong&gt; Sort by monthly cost. Usually 3-5 functions account for 80% of your Lambda bill.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Group by latency requirement.&lt;/strong&gt; Functions that need sub-100ms response go into your primary service. Batch functions can be background workers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Containerize incrementally.&lt;/strong&gt; Move one function at a time. Keep Lambda as a fallback for 2 weeks after each migration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Right-size from day one.&lt;/strong&gt; Use actual traffic data to size your containers. Don't guess — check CloudWatch metrics for peak and average utilization.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Containers → Lambda:
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Identify low-utilization services.&lt;/strong&gt; If a container averages under 10% CPU, it's a candidate for serverless.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check for state dependencies.&lt;/strong&gt; Any in-memory cache, connection pool, or loaded model means the function needs provisioned concurrency — factor that cost in.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extract event-driven logic.&lt;/strong&gt; Cron jobs, webhook handlers, and async processors are the lowest-risk migrations.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Common Mistakes
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;"Serverless is always cheaper."&lt;/strong&gt; It's cheaper at low scale. It's expensive at high scale. The marketing materials show the low-scale numbers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Containers are too complex."&lt;/strong&gt; Fargate and Cloud Run eliminate most operational overhead. You don't need to manage EC2 instances to run containers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"We'll optimize Lambda later."&lt;/strong&gt; Lambda cost optimization (reducing memory, optimizing cold starts, batching) has diminishing returns. If the architecture is wrong, no optimization saves you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Our traffic might spike someday."&lt;/strong&gt; Design for today's traffic with the ability to scale. Don't pay 6x more today because traffic might spike in 18 months. You can migrate later.&lt;/p&gt;

&lt;h2&gt;
  
  
  The One Question That Cuts Through the Debate
&lt;/h2&gt;

&lt;p&gt;Ask yourself: "If my traffic doubles next month, does my bill double or stay roughly the same?"&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If it doubles: you're on serverless or poorly-sized containers.&lt;/li&gt;
&lt;li&gt;If it stays roughly the same: you're on well-sized containers.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For sustained workloads, you want the second answer. For unpredictable workloads, the first answer is actually correct — you'd rather your bill scale with actual usage than pay for unused capacity.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;At &lt;a href="https://www.techsaas.cloud/services/" rel="noopener noreferrer"&gt;TechSaaS&lt;/a&gt;, we help teams make this decision with real data, not gut feelings. We'll analyze your current architecture, run the cost comparison, and build the migration plan if the math says you should move. Most clients save 40-70% on their cloud bill within the first month after migration.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>tutorial</category>
      <category>programming</category>
      <category>devops</category>
    </item>
    <item>
      <title>Self-Hosting in 2026: The Complete Infrastructure Stack (82 Containers, $0 Cloud Bill)</title>
      <dc:creator>Yash Pritwani</dc:creator>
      <pubDate>Wed, 22 Apr 2026 06:00:21 +0000</pubDate>
      <link>https://dev.to/yash_pritwani_07a77613fd6/self-hosting-in-2026-the-complete-infrastructure-stack-82-containers-0-cloud-bill-5e47</link>
      <guid>https://dev.to/yash_pritwani_07a77613fd6/self-hosting-in-2026-the-complete-infrastructure-stack-82-containers-0-cloud-bill-5e47</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://www.techsaas.cloud/blog/self-hosting-2026-infrastructure-stack" rel="noopener noreferrer"&gt;TechSaaS Cloud&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://www.techsaas.cloud/blog/self-hosting-2026-infrastructure-stack" rel="noopener noreferrer"&gt;TechSaaS Cloud&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  Self-Hosting in 2026: The Complete Infrastructure Stack
&lt;/h1&gt;

&lt;p&gt;We run 82 production containers on a single physical server. Grafana, Prometheus, Gitea, Directus CMS, n8n automation, Loki logging, PostgreSQL, Redis, FalkorDB, multiple AI models, and dozens of web applications. Our monthly cloud bill is zero dollars.&lt;/p&gt;

&lt;p&gt;This isn't a hobby project. This is a production infrastructure serving real users, with 99.9% uptime over the last year, automated backups, monitoring that pages us before users notice issues, and CI/CD that deploys on every push.&lt;/p&gt;

&lt;p&gt;The 2026 self-hosting stack is a fundamentally different proposition than it was even two years ago. The tooling has matured. Docker Compose handles orchestration that once required Kubernetes. Cloudflare Tunnels provide zero-trust access without opening any ports. Reverse proxies auto-provision SSL certificates. And the economics have shifted — cloud costs have risen 15-20% while hardware costs have dropped.&lt;/p&gt;

&lt;p&gt;Here's the exact stack, why we chose each component, and the real numbers.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Hardware Layer
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Proxmox VE&lt;/strong&gt; as the hypervisor. Running LXC containers for lightweight isolation between tenants. The host server has 13GB RAM, NVMe storage across multiple logical volumes, and an NVIDIA GTX 1650 for AI inference workloads.&lt;/p&gt;

&lt;p&gt;Why Proxmox over bare Linux? Live migration, snapshot-based backups, web UI for emergency management, and proper resource isolation between workloads. It's the enterprise hypervisor that's actually free.&lt;/p&gt;

&lt;p&gt;Storage layout:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;/mnt/containers&lt;/code&gt; (148GB): Docker data root — all container images and volumes&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/mnt/projects&lt;/code&gt; (84GB): Git repositories, CI/CD artifacts, application code&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/mnt/databases&lt;/code&gt; (15GB): PostgreSQL, Redis, FalkorDB, SQLite databases&lt;/li&gt;
&lt;li&gt;Root (69GB): OS, configs, logs&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Networking Layer
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Traefik&lt;/strong&gt; as the reverse proxy. Auto-discovers Docker containers via labels, provisions Let's Encrypt SSL, handles routing, load balancing, and rate limiting. Configuration is entirely label-based — no nginx configs to maintain.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cloudflare Tunnels&lt;/strong&gt; for zero-trust access. No ports open on the firewall. Not 80, not 443, not SSH. Everything routes through Cloudflare's network, which handles DDoS protection, CDN caching, and access control. This is genuinely more secure than most cloud deployments.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Authelia&lt;/strong&gt; for single sign-on. One login across all 82 services. TOTP two-factor authentication. Session management. Access policies per-service. No paying $15/user/month for Auth0 or Okta.&lt;/p&gt;

&lt;p&gt;The networking stack gives us: automatic SSL, zero-trust access, SSO, DDoS protection, and CDN caching. Total cost: $0 (Cloudflare free tier + open-source tools).&lt;/p&gt;

&lt;h2&gt;
  
  
  The Monitoring Stack
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Prometheus&lt;/strong&gt; scrapes metrics from every container every 15 seconds. Recording rules pre-compute expensive queries. 90-day retention.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Grafana&lt;/strong&gt; visualizes everything. Three-tier dashboard hierarchy: overview, service detail, and debug dashboards. Burn-rate alerts instead of static thresholds to minimize false alarms.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Loki + Promtail&lt;/strong&gt; for centralized logging. Every container's stdout goes to Loki, queryable via the same Grafana interface. LogQL queries correlate logs with metrics during incidents.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Uptime Kuma&lt;/strong&gt; for external monitoring. 28 monitors checking every service from outside the network. If our server is unreachable, we know within 60 seconds.&lt;/p&gt;

&lt;p&gt;Alert routing: Prometheus → Grafana → ntfy push notifications → phone. Average alert-to-acknowledgment time: 3 minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The CI/CD Layer
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Gitea&lt;/strong&gt; as the Git host. Self-hosted GitHub alternative with Actions support. All repositories push-mirror to GitHub for redundancy, but Gitea is the primary for development.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gitea Actions&lt;/strong&gt; for CI/CD. Docker-based runners execute on the same host. Build, test, security scan, deploy — all triggered on push. Average pipeline: 90 seconds from push to production.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Docker Registry&lt;/strong&gt; (self-hosted). Built images stay local. No pulling from Docker Hub on every deploy. Faster, more reliable, no rate limits.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Data Layer
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;PostgreSQL&lt;/strong&gt; for relational data. Shared across services with schema-level isolation. Daily automated backups with point-in-time recovery.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Redis&lt;/strong&gt; for caching and session storage. Sub-millisecond reads. Pub/sub for real-time features.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;FalkorDB&lt;/strong&gt; for graph data. Knowledge graphs, relationship mapping, semantic search. Runs on the Redis wire protocol.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SQLite&lt;/strong&gt; for lightweight applications that don't need a full PostgreSQL database. Sometimes the right answer is the simplest one.&lt;/p&gt;

&lt;p&gt;All databases back up nightly to an off-site location. Retention: 30 days of daily snapshots.&lt;/p&gt;

&lt;h2&gt;
  
  
  The AI/ML Layer
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Ollama&lt;/strong&gt; running Gemma and other open-source models. Local inference on the GTX 1650 — 4GB VRAM is enough for 7B models quantized to 4-bit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;vLLM&lt;/strong&gt; for production inference endpoints. OpenAI-compatible API. Model swapping without downtime.&lt;/p&gt;

&lt;p&gt;This is why the GTX 1650 is in the server. For $200 in hardware, we have unlimited local AI inference with no per-token API costs. Classification, summarization, embedding generation — all free after the hardware purchase.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Automation Layer
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;n8n&lt;/strong&gt; for workflow automation. 14 active workflows handling: content scheduling, email processing, social media posting, webhook routing, and monitoring integrations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Cron + systemd&lt;/strong&gt; for lightweight scheduling. Anything that doesn't need n8n's visual builder runs as a systemd timer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Custom scripts&lt;/strong&gt; for domain-specific automation. LinkedIn growth engine, content pipeline dispatcher, analytics collection — all containerized, all monitored.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Cost Comparison
&lt;/h2&gt;

&lt;p&gt;Here's what the equivalent infrastructure would cost on AWS:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th&gt;Self-Hosted&lt;/th&gt;
&lt;th&gt;AWS Equivalent&lt;/th&gt;
&lt;th&gt;Monthly AWS Cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Compute (82 containers)&lt;/td&gt;
&lt;td&gt;Included&lt;/td&gt;
&lt;td&gt;ECS/Fargate&lt;/td&gt;
&lt;td&gt;$1,200&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PostgreSQL&lt;/td&gt;
&lt;td&gt;Included&lt;/td&gt;
&lt;td&gt;RDS&lt;/td&gt;
&lt;td&gt;$180&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Redis&lt;/td&gt;
&lt;td&gt;Included&lt;/td&gt;
&lt;td&gt;ElastiCache&lt;/td&gt;
&lt;td&gt;$120&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Monitoring (Prometheus+Grafana)&lt;/td&gt;
&lt;td&gt;Included&lt;/td&gt;
&lt;td&gt;CloudWatch + Managed Grafana&lt;/td&gt;
&lt;td&gt;$350&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CI/CD&lt;/td&gt;
&lt;td&gt;Included&lt;/td&gt;
&lt;td&gt;CodePipeline + ECR&lt;/td&gt;
&lt;td&gt;$80&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Git hosting&lt;/td&gt;
&lt;td&gt;Included&lt;/td&gt;
&lt;td&gt;CodeCommit or GitHub Teams&lt;/td&gt;
&lt;td&gt;$50&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reverse proxy + SSL&lt;/td&gt;
&lt;td&gt;Included&lt;/td&gt;
&lt;td&gt;ALB + ACM&lt;/td&gt;
&lt;td&gt;$100&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AI inference&lt;/td&gt;
&lt;td&gt;Included&lt;/td&gt;
&lt;td&gt;SageMaker&lt;/td&gt;
&lt;td&gt;$300&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Logging&lt;/td&gt;
&lt;td&gt;Included&lt;/td&gt;
&lt;td&gt;CloudWatch Logs&lt;/td&gt;
&lt;td&gt;$150&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~$30 electricity&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;$2,530/month&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That's $30,360 per year in cloud costs eliminated. The server hardware (roughly $2,000) paid for itself in the first month.&lt;/p&gt;

&lt;h2&gt;
  
  
  When Self-Hosting Is Wrong
&lt;/h2&gt;

&lt;p&gt;Self-hosting isn't for everyone. Don't do this if:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;You don't have someone who understands Linux.&lt;/strong&gt; When things break at 3am, you need someone who can SSH in and debug.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You need five-nines uptime.&lt;/strong&gt; Single-server self-hosting gives you three nines to four nines. For five nines, you need geographic redundancy that cloud provides naturally.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your team is tiny and shipping is the priority.&lt;/strong&gt; If you're a team of 3 racing to product-market fit, managed services save engineering time that's better spent on product.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compliance requires specific certifications.&lt;/strong&gt; SOC2, HIPAA, PCI compliance is dramatically easier with certified cloud infrastructure than self-hosted.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Self-hosting makes sense when you have sustained workloads, predictable traffic, a team that can maintain infrastructure, and a desire for full control and zero vendor lock-in.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;The 2026 self-hosting starter path:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Start small.&lt;/strong&gt; One used mini-PC ($200-400), Proxmox, Docker Compose with Traefik + monitoring.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add services incrementally.&lt;/strong&gt; Move one cloud service at a time. Start with the expensive ones.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cloudflare Tunnel from day one.&lt;/strong&gt; Zero-trust access without port forwarding. Secure by default.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Monitor everything.&lt;/strong&gt; Prometheus + Grafana + alerting before you add any production workloads.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backup to external storage.&lt;/strong&gt; Never have all your eggs in one physical location.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The complete Docker Compose file for our 82-service stack is available in the guide linked below. It's opinionated, tested in production for over a year, and ready to deploy.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;At &lt;a href="https://www.techsaas.cloud/services/" rel="noopener noreferrer"&gt;TechSaaS&lt;/a&gt;, we help teams design and implement self-hosted infrastructure that matches or exceeds cloud reliability. Whether you're repatriating from cloud or building from scratch, we bring the architecture expertise so your team doesn't have to learn through expensive mistakes.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>tutorial</category>
      <category>programming</category>
      <category>devops</category>
    </item>
    <item>
      <title>SaaS Metrics That Matter: The 5 Numbers Your Board Should Actually Care About</title>
      <dc:creator>Yash Pritwani</dc:creator>
      <pubDate>Wed, 22 Apr 2026 06:00:18 +0000</pubDate>
      <link>https://dev.to/yash_pritwani_07a77613fd6/saas-metrics-that-matter-the-5-numbers-your-board-should-actually-care-about-5hek</link>
      <guid>https://dev.to/yash_pritwani_07a77613fd6/saas-metrics-that-matter-the-5-numbers-your-board-should-actually-care-about-5hek</guid>
      <description>&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://www.techsaas.cloud/blog/saas-metrics-beyond-mrr-churn" rel="noopener noreferrer"&gt;TechSaaS Cloud&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on &lt;a href="https://www.techsaas.cloud/blog/saas-metrics-beyond-mrr-churn" rel="noopener noreferrer"&gt;TechSaaS Cloud&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  SaaS Metrics That Matter: The 5 Numbers Your Board Should Actually Care About
&lt;/h1&gt;

&lt;p&gt;Every SaaS board meeting starts the same way. MRR is up. Churn is down. Everyone nods. The meeting ends.&lt;/p&gt;

&lt;p&gt;Six months later the company is running out of runway and nobody can explain why the numbers looked good but the business isn't working.&lt;/p&gt;

&lt;p&gt;MRR and churn are lagging indicators. They tell you what already happened. They're the rearview mirror of your business. By the time churn spikes, the customers have already been unhappy for months. By the time MRR stalls, the pipeline has been dry for a quarter.&lt;/p&gt;

&lt;p&gt;After building analytics infrastructure for 12 SaaS companies — from seed-stage to Series C — these are the five metrics that actually predict whether your business will be alive in 18 months. They're leading indicators. They tell you what's about to happen, not what already did.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Net Revenue Retention (NRR)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What it is:&lt;/strong&gt; The percentage of revenue you retain from existing customers after accounting for upgrades, downgrades, and churn. It's your expansion revenue minus your contraction and churn.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Formula:&lt;/strong&gt; (Starting MRR + Expansion - Contraction - Churned MRR) / Starting MRR × 100&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it matters more than gross churn:&lt;/strong&gt; A 5% monthly churn rate sounds bad. But if your remaining customers are expanding by 8%, your NRR is 103% — meaning you grow even without new customers. Gross churn hides this crucial nuance.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Below 90%: You're dying. Existing customers are shrinking faster than you can replace them.&lt;/li&gt;
&lt;li&gt;90-100%: Treading water. Growth depends entirely on new customer acquisition.&lt;/li&gt;
&lt;li&gt;100-110%: Healthy. Some organic growth from existing base.&lt;/li&gt;
&lt;li&gt;110-120%: Strong. Expansion is a meaningful growth engine.&lt;/li&gt;
&lt;li&gt;120%+: Elite. You could stop selling to new customers and still grow. Snowflake (130%), Datadog (125%), Twilio (127%).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The insight:&lt;/strong&gt; If your NRR is below 100%, your sales team is filling a leaky bucket. Every new customer you close is partially offset by revenue you're losing from existing customers. Fix retention before you scale acquisition.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Revenue Per Employee (RPE)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What it is:&lt;/strong&gt; Total ARR divided by total headcount. The simplest measure of organizational efficiency.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why your board should watch this:&lt;/strong&gt; It's the earliest warning sign of an unsustainable business. You can grow MRR by hiring more salespeople, but if revenue per employee is declining, you're buying growth by spending future runway.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Below $100K RPE: Dangerously inefficient. Common in early-stage with heavy R&amp;amp;D investment.&lt;/li&gt;
&lt;li&gt;$100K-$200K RPE: Acceptable for growth-stage companies still building the product.&lt;/li&gt;
&lt;li&gt;$200K-$300K RPE: Healthy. The team is productive and the product sells efficiently.&lt;/li&gt;
&lt;li&gt;$300K+ RPE: Highly efficient. Usually means strong product-led growth or excellent sales efficiency.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The insight:&lt;/strong&gt; When RPE drops two quarters in a row, you're hiring faster than you're growing. Either your new hires aren't productive yet (3-6 month ramp), or you're over-hiring for the growth rate. Either way, the board should be asking why before the next round of fundraising.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Time to Value (TTV)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What it is:&lt;/strong&gt; The number of days between a customer signing up and reaching their first meaningful outcome — the "aha moment" that makes them sticky.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it predicts survival:&lt;/strong&gt; TTV directly correlates with retention. Customers who reach value in the first week retain at 2-3x the rate of customers who take a month. Every day of friction between signup and value is a day the customer might leave.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How to measure it:&lt;/strong&gt; Define your activation event — the action that correlates most strongly with long-term retention. For Slack, it was 2,000 messages sent. For Zoom, it was the first meeting with 3+ participants. For your product, it's the specific action after which customers almost never churn.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Under 1 day: Exceptional. Usually product-led with self-serve onboarding.&lt;/li&gt;
&lt;li&gt;1-7 days: Good. Most successful B2B SaaS hits value within a week.&lt;/li&gt;
&lt;li&gt;7-30 days: Acceptable for complex enterprise products with implementation requirements.&lt;/li&gt;
&lt;li&gt;30+ days: Dangerous. You're losing customers before they ever experience the product.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The insight:&lt;/strong&gt; If TTV is growing, your product is getting more complex without getting more valuable. Simplify onboarding. Remove steps. Pre-configure everything possible. The fastest path to retention is the fastest path to value.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Expansion Revenue Percentage
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What it is:&lt;/strong&gt; The percentage of your new MRR that comes from existing customers upgrading, buying add-ons, or increasing usage — versus new logo acquisition.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Formula:&lt;/strong&gt; Expansion MRR / Total New MRR × 100&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it's more important than new customer count:&lt;/strong&gt; Expansion revenue costs 5-7x less to acquire than new logo revenue. Your CAC for existing customers is nearly zero — they already trust you, already have the product integrated, already know the value. Every dollar of expansion revenue has dramatically better unit economics.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Below 20%: Over-dependent on new sales. Your existing customers aren't finding enough value to pay more.&lt;/li&gt;
&lt;li&gt;20-30%: Healthy mix. Most growth is net-new but expansion contributes meaningfully.&lt;/li&gt;
&lt;li&gt;30-40%: Strong. Your product grows with customers. Pricing captures increasing value.&lt;/li&gt;
&lt;li&gt;40%+: Exceptional. Your product is truly embedded in customer workflows and grows as they grow.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The insight:&lt;/strong&gt; If expansion revenue is below 20%, ask yourself: do customers have a natural path to pay you more? Is there usage-based pricing? Are there meaningful add-ons? If the only way to grow revenue is acquiring new logos, you're building a linear business in a world that rewards compounding ones.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. CAC Payback Period
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What it is:&lt;/strong&gt; The number of months it takes to recoup the cost of acquiring a customer through their recurring revenue. It tells you how long your money is "underwater" before a customer becomes profitable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Formula:&lt;/strong&gt; CAC / (MRR × Gross Margin %)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why it's better than CAC alone:&lt;/strong&gt; A $50K CAC is fine if the customer pays $10K/month. A $5K CAC is terrible if the customer pays $100/month and churns at month 6. Payback period contextualizes acquisition cost against the actual revenue pattern.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;Under 6 months: Excellent. Money comes back fast. You can reinvest aggressively.&lt;/li&gt;
&lt;li&gt;6-12 months: Good. Standard for healthy B2B SaaS. Most VCs expect this.&lt;/li&gt;
&lt;li&gt;12-18 months: Concerning. Cash is tied up too long. Growth requires heavy funding.&lt;/li&gt;
&lt;li&gt;18+ months: Unsustainable without deep pockets. You're essentially lending money to customers for over a year before seeing returns.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The insight:&lt;/strong&gt; If your payback period is growing while your sales velocity stays the same, you're spending more to acquire lower-value customers. Either your ICP has shifted, your pricing is wrong, or your sales team is going downmarket to hit targets.&lt;/p&gt;

&lt;h2&gt;
  
  
  Putting It Together: The Dashboard That Matters
&lt;/h2&gt;

&lt;p&gt;Stop showing your board a wall of 20 metrics. Show them these five in a single-page dashboard with trend lines:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Current&lt;/th&gt;
&lt;th&gt;Trend&lt;/th&gt;
&lt;th&gt;Target&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;NRR&lt;/td&gt;
&lt;td&gt;108%&lt;/td&gt;
&lt;td&gt;↑&lt;/td&gt;
&lt;td&gt;&amp;gt;115%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RPE&lt;/td&gt;
&lt;td&gt;$185K&lt;/td&gt;
&lt;td&gt;→&lt;/td&gt;
&lt;td&gt;&amp;gt;$200K&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TTV&lt;/td&gt;
&lt;td&gt;4.2 days&lt;/td&gt;
&lt;td&gt;↓&lt;/td&gt;
&lt;td&gt;&amp;lt;3 days&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Expansion Revenue&lt;/td&gt;
&lt;td&gt;24%&lt;/td&gt;
&lt;td&gt;↑&lt;/td&gt;
&lt;td&gt;&amp;gt;30%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CAC Payback&lt;/td&gt;
&lt;td&gt;9.8 mo&lt;/td&gt;
&lt;td&gt;→&lt;/td&gt;
&lt;td&gt;&amp;lt;8 months&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If all five trend in the right direction, your business is compounding. If two or more are trending wrong, you have a structural problem that MRR growth is masking. The board should be asking about root causes, not celebrating topline numbers.&lt;/p&gt;

&lt;h2&gt;
  
  
  The One Metric to Rule Them All
&lt;/h2&gt;

&lt;p&gt;If you can only track one beyond MRR, make it NRR. It's the single strongest predictor of SaaS outcomes. Companies with NRR above 120% have a 95% probability of reaching $100M ARR if they maintain it for 3+ years. Companies below 90% NRR almost never recover without a fundamental product change.&lt;/p&gt;

&lt;p&gt;NRR is the metric that tells you whether your product is genuinely solving a growing problem for your customers, or whether you're churning through them faster than you can find new ones.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;At &lt;a href="https://www.techsaas.cloud/services/" rel="noopener noreferrer"&gt;TechSaaS&lt;/a&gt;, we build custom analytics dashboards that make these metrics visible, actionable, and automated. If your board is still squinting at spreadsheets instead of real-time dashboards, we should talk.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>tutorial</category>
      <category>programming</category>
      <category>devops</category>
    </item>
    <item>
      <title>Togel singapore</title>
      <dc:creator>staffhose</dc:creator>
      <pubDate>Wed, 22 Apr 2026 05:34:18 +0000</pubDate>
      <link>https://dev.to/dinna_yolandatanjung_798/togel-singapore-157h</link>
      <guid>https://dev.to/dinna_yolandatanjung_798/togel-singapore-157h</guid>
      <description>&lt;p&gt;Togel Singapore dimulai pada 1968 untuk mendanai pembangunan negara. Seiring waktu, menjadi salah satu permainan judi paling populer di Asia &lt;br&gt;
Wajib coba : &lt;a href="http://info.ketuaprediksi.com/" rel="noopener noreferrer"&gt;http://info.ketuaprediksi.com/&lt;/a&gt;&lt;/p&gt;

</description>
      <category>casino</category>
      <category>tutorial</category>
      <category>webpack</category>
      <category>podcast</category>
    </item>
    <item>
      <title>How to Build an AI Chatbot for Your Business (Step-by-Step Guide for 2026)</title>
      <dc:creator>Ash Bagda</dc:creator>
      <pubDate>Wed, 22 Apr 2026 05:32:25 +0000</pubDate>
      <link>https://dev.to/ash_bagda_fbcbf74c091110f/how-to-build-an-ai-chatbot-for-your-business-step-by-step-guide-for-2026-c5k</link>
      <guid>https://dev.to/ash_bagda_fbcbf74c091110f/how-to-build-an-ai-chatbot-for-your-business-step-by-step-guide-for-2026-c5k</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;The short answer:&lt;/strong&gt; Building an AI chatbot in 2026 is a 6-phase process: define scope, choose the right platform, connect your data, train on real conversations, test failure modes, then launch with a human fallback. Most projects fail in phases 3 and 4, not phase 1.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Most guides on this topic start with "choose a platform" and end with "you're live." That's the optimistic version. The reality has more steps in the middle that nobody warns you about, specifically around data preparation and what happens when the bot doesn't know something.&lt;/p&gt;

&lt;p&gt;This guide covers the full process, including the parts that slow projects down.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why most chatbot builds fail before they start
&lt;/h2&gt;

&lt;p&gt;The standard advice is: pick a chatbot tool, connect it to your website, write some FAQs, go live. That works for simple use cases. For anything involving real customer data, CRM integration, or meaningful deflection rates, it breaks quickly.&lt;/p&gt;

&lt;p&gt;Most teams start by evaluating vendors. That's backwards. Until you know what data the bot needs to access, what conversations it needs to handle, and what your fallback process looks like, you can't evaluate platforms meaningfully. You end up choosing based on the demo rather than your actual requirements.&lt;/p&gt;

&lt;p&gt;The other failure point is training data. AI chatbots learn from examples. If you train on your FAQ page, you get a bot that answers FAQ-page questions. Real customer queries are messier, more varied, and often completely different from what your marketing team wrote in the help docs. Teams that skip this discovery step launch bots with 20–30% coverage rates, which frustrates users and delivers no ROI.&lt;/p&gt;




&lt;h2&gt;
  
  
  Phase 1: Define scope before touching any tool
&lt;/h2&gt;

&lt;p&gt;Before any vendor is involved, decide what the bot will and won't handle. This is the decision most teams skip, and it's why most bots launch underperforming.&lt;/p&gt;

&lt;p&gt;Pull your last 3 months of support tickets, chat logs, or email inquiries. Categorise them. You're looking for clusters: groups of similar questions that appear repeatedly. Anything that clusters at 5% or more of total volume is a candidate for automation.&lt;/p&gt;

&lt;p&gt;Then apply a simple filter to each cluster:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Does answering this require data from a system (CRM, inventory, orders)? If yes, mark it as "integrated" (harder to build, but higher ROI).&lt;/li&gt;
&lt;li&gt;Does answering this require human judgement every time? If yes, exclude it from scope.&lt;/li&gt;
&lt;li&gt;Is the answer relatively stable, or does it change frequently? Frequently changing answers need a content management process behind them.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;By the end of this phase you should have a list of in-scope topics, an estimate of what percentage of total volume they represent, and a list of systems the bot will need to connect to.&lt;/p&gt;

&lt;p&gt;The most common mistake is including everything. A bot scoped to handle 80% of queries in v1 will take 3x longer to build and launch with lower quality than one that handles 30% well.&lt;/p&gt;




&lt;h2&gt;
  
  
  Phase 2: Choose your platform based on requirements, not demos
&lt;/h2&gt;

&lt;p&gt;Vendor selection should happen after scope definition, not before. Use the integration list from phase 1 as your filter.&lt;/p&gt;

&lt;p&gt;Take your list of required system integrations and ask every vendor the same question: "Can you connect to X natively, or does this require custom development?" The answer tells you more than any feature comparison.&lt;/p&gt;

&lt;p&gt;The other question that matters: "How do we update content after launch?" If the answer involves a developer every time, plan for slow iteration.&lt;/p&gt;

&lt;p&gt;For most business deployments in 2026, the decision comes down to three options. Low-code platforms (Voiceflow, Botpress, similar) build faster with less flexibility on integrations — good for contained use cases. LLM-native builds (custom GPT wrappers, Claude API, similar) are more flexible but require more technical ownership, better for complex or frequently changing use cases. All-in-one CX platforms (Intercom, Zendesk AI, similar) are easiest if you're already on their helpdesk, but limited when you need custom logic.&lt;/p&gt;

&lt;p&gt;There's no universally correct answer. The right choice depends on your technical capacity and what systems you need to integrate.&lt;/p&gt;




&lt;h2&gt;
  
  
  Phase 3: Connect your data (this is where most projects stall)
&lt;/h2&gt;

&lt;p&gt;This phase has two components that teams frequently confuse, and mixing them up is what causes timelines to slip.&lt;/p&gt;

&lt;p&gt;The first is knowledge base content: documents, FAQs, product information, policies. This is the easier part. Most platforms have document ingestion built in.&lt;/p&gt;

&lt;p&gt;The second is live system integrations, connecting to your CRM, order management system, inventory database, or booking system so the bot can look up real-time data about a specific customer or product. This is where projects slow down.&lt;/p&gt;

&lt;p&gt;For each integration you need an API endpoint that returns the data, authentication that works in the chatbot environment, and error handling for when the system returns nothing or returns an error.&lt;/p&gt;

&lt;p&gt;Don't underestimate the error handling. A bot that crashes when the CRM returns an empty result is worse than a bot with no integration at all. An e-commerce company connecting order tracking needs to handle at least five states: order found and shipped, order found and processing, order not found, order status unknown, and system unavailable. Each needs a different response.&lt;/p&gt;




&lt;h2&gt;
  
  
  Phase 4: Train on real conversations, not ideal ones
&lt;/h2&gt;

&lt;p&gt;Pull 200–300 real customer messages from your support history for each in-scope topic. Look at the full range, not just the clean well-worded questions but the short ones, the misspelled ones, the ones that combine two topics in one message.&lt;/p&gt;

&lt;p&gt;For LLM-based bots, this training takes the form of examples and instructions in the system prompt rather than traditional ML training. You're teaching the model what this question looks like in practice, what a good answer contains, and what to do when the query is ambiguous.&lt;/p&gt;

&lt;p&gt;For rule-based or intent-based platforms, this means building out alternative phrasings for each intent. Most teams build 3–5 examples per intent. The bots that work well have 15–20.&lt;/p&gt;

&lt;p&gt;I've found that the questions that break bots most often are the two-part ones: "How do I return this and will I get a full refund?" Those require the bot to recognise multiple intents in one message and respond to both. Worth testing these specifically before launch.&lt;/p&gt;




&lt;h2&gt;
  
  
  Phase 5: Test failure modes, not just happy paths
&lt;/h2&gt;

&lt;p&gt;Most QA processes test the questions the bot is supposed to answer. That's necessary but not enough. You also need to test:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Questions just outside scope (what does the bot do when it doesn't know?)&lt;/li&gt;
&lt;li&gt;Ambiguous questions with multiple valid interpretations&lt;/li&gt;
&lt;li&gt;Hostile or frustrated inputs ("this is useless, I want a human")&lt;/li&gt;
&lt;li&gt;Questions in different languages if multilingual support is claimed&lt;/li&gt;
&lt;li&gt;Edge cases in integrations (expired sessions, malformed data, empty results)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The bot's behaviour when it fails matters as much as when it succeeds. A bot that says "I'm not sure, let me connect you with someone who can help" and hands off cleanly is far better than one that gives a wrong answer confidently.&lt;/p&gt;

&lt;p&gt;Define your fallback logic before launch. What triggers a handoff, what information gets passed to the human agent, and how the handoff appears to the customer.&lt;/p&gt;




&lt;h2&gt;
  
  
  Phase 6: Launch with a human fallback, then expand scope
&lt;/h2&gt;

&lt;p&gt;Resist the pressure to launch with everything. Launch with your highest-volume, lowest-complexity cluster first. Get real usage data. See what questions come in that you didn't anticipate. Use that to improve coverage before adding the next cluster.&lt;/p&gt;

&lt;p&gt;Monitor these metrics weekly in the first month:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Containment rate: percentage of conversations handled without escalation&lt;/li&gt;
&lt;li&gt;Escalation reason: why did the bot hand off?&lt;/li&gt;
&lt;li&gt;User satisfaction on bot-handled conversations (simple thumbs up/down is enough)&lt;/li&gt;
&lt;li&gt;False positive rate: bot answered but gave the wrong answer&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The goal in month 1 is not maximum deflection. It's understanding what the bot gets right and what it gets wrong. That understanding is what makes month 3 significantly better.&lt;/p&gt;




&lt;h2&gt;
  
  
  Realistic timeline to expect
&lt;/h2&gt;

&lt;p&gt;A mid-complexity deployment runs 8–12 weeks from kick-off to live. Here's how that typically breaks down:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Weeks 1–2: Scope definition and ticket analysis&lt;/li&gt;
&lt;li&gt;Weeks 3–4: Vendor selection and integration scoping&lt;/li&gt;
&lt;li&gt;Weeks 5–7: Platform setup, knowledge base, and integrations&lt;/li&gt;
&lt;li&gt;Weeks 8–9: Training on real conversations and QA&lt;/li&gt;
&lt;li&gt;Week 10: Soft launch on limited traffic&lt;/li&gt;
&lt;li&gt;Weeks 11–12: Data review and first iteration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Simple FAQ-only bots can move faster. Anything with 3+ system integrations will likely need more time in weeks 5–7.&lt;/p&gt;




&lt;h2&gt;
  
  
  Who this works best for
&lt;/h2&gt;

&lt;p&gt;This process works well for businesses that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Have enough conversation volume to justify the build (50+ queries per day minimum)&lt;/li&gt;
&lt;li&gt;Have technical resource to own integrations, or a vendor who will&lt;/li&gt;
&lt;li&gt;Are willing to dedicate 2–3 weeks to the scope and training data phases before building anything&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The teams at &lt;a href="https://fnatechnology.com" rel="noopener noreferrer"&gt;FNA Technology&lt;/a&gt; work with businesses across the Gulf region on exactly this process. In most cases the scope and data phases are what determine whether a deployment succeeds, not the platform chosen.&lt;/p&gt;




&lt;h2&gt;
  
  
  Who this is NOT for
&lt;/h2&gt;

&lt;p&gt;Being honest about the limits is more useful than pretending otherwise.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Teams that need something live in two weeks. Rushed builds produce bots with low coverage and poor failure handling — which damages trust with customers faster than having no bot at all.&lt;/li&gt;
&lt;li&gt;Businesses where every query is unique. Chatbots return value at scale on repeatable questions. If your support is 80% bespoke, the ROI isn't there.&lt;/li&gt;
&lt;li&gt;Companies without CRM or system data to connect to, who are hoping the bot will somehow personalise responses. Without live data access, personalisation isn't possible.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Q: Do I need a developer to build an AI chatbot?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For FAQ-only bots using low-code platforms, no. For anything requiring CRM or system integrations, yes, either in-house or through a vendor who owns the integration work. The AI layer is increasingly no-code. The data plumbing still requires technical skill.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: What's a realistic budget for a business chatbot in 2026?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A basic deployment on a low-code platform runs $3,000–$8,000 in setup, plus $200–$800/month in platform fees depending on volume. A custom-built LLM bot with multiple integrations runs $15,000–$50,000 in initial build cost. Ongoing maintenance is often underbudgeted. Plan for 10–15% of build cost annually.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: How do I measure whether the chatbot is working?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Containment rate (conversations resolved without human handoff) is the primary metric. Secondary metrics: average handle time on escalated conversations (should drop as the bot improves pre-summarisation), and customer satisfaction scores on bot-handled conversations. Revenue impact is measurable in sales qualification use cases. Track demo conversion rate before and after.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: Can the same chatbot handle WhatsApp and website?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Most modern platforms support multichannel deployment from a single bot configuration. The conversation logic is the same; the channel is a delivery layer. WhatsApp has specific rules around message templates for outbound messaging that add some complexity, but for inbound query handling, the setup is largely the same. For businesses in the Gulf region where WhatsApp is the primary customer channel, the &lt;a href="https://fnatechnology.com/services/whatsapp-chatbot-development-company" rel="noopener noreferrer"&gt;WhatsApp chatbot development service&lt;/a&gt; is built specifically for that context.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: What happens when the bot gets something wrong?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is the question most teams don't ask until after launch. The answer should be built into your fallback logic before you go live. At minimum: the bot should detect low-confidence responses and offer to connect the user with a human rather than guessing. Logging incorrect responses for review and correction should be a weekly process in the first 90 days.&lt;/p&gt;




&lt;p&gt;If you'd rather have an experienced team handle the build, explore real-world use cases and the full range of services here:&lt;br&gt;
👉&lt;a href="https://fnatechnology.com/services" rel="noopener noreferrer"&gt;FnA Technology LLP &lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>data</category>
      <category>llm</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>The Complete Guide to AI Tutors in India 2026</title>
      <dc:creator>Rahul Jaiswal</dc:creator>
      <pubDate>Wed, 22 Apr 2026 05:30:17 +0000</pubDate>
      <link>https://dev.to/jaiswal007/the-complete-guide-to-ai-tutors-in-india-2026-3c0j</link>
      <guid>https://dev.to/jaiswal007/the-complete-guide-to-ai-tutors-in-india-2026-3c0j</guid>
      <description>&lt;h1&gt;
  
  
  The Complete Guide to AI Tutors in India 2026
&lt;/h1&gt;

&lt;p&gt;If you are a student in India looking for an AI tutor, this guide covers everything you need to know — what AI tutors actually do, how they compare to human tutors, which ones are worth using, and how to get the most out of them.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is an AI Tutor?
&lt;/h2&gt;

&lt;p&gt;An AI tutor is an artificial intelligence system that teaches you like a human tutor would — explaining concepts, solving your doubts, adapting to your learning pace, and testing your understanding. Unlike a chatbot that just answers questions, a good AI tutor structures lessons, checks comprehension, and adjusts its teaching approach based on how you respond.&lt;/p&gt;

&lt;p&gt;The best &lt;a href="https://easelearn.ai/ai-tutor" rel="noopener noreferrer"&gt;AI tutor&lt;/a&gt; available in India right now is &lt;a href="https://easelearn.ai" rel="noopener noreferrer"&gt;EaseLearn AI&lt;/a&gt;. It combines camera-based &lt;a href="https://easelearn.ai/doubt-solver" rel="noopener noreferrer"&gt;doubt solving&lt;/a&gt;, adaptive AI tutoring, &lt;a href="https://easelearn.ai/math-solver" rel="noopener noreferrer"&gt;math solving&lt;/a&gt;, and a patent-pending &lt;a href="https://easelearn.ai/immersive-classroom" rel="noopener noreferrer"&gt;Immersive Classroom&lt;/a&gt; that generates live visual lessons in real-time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Indian Students Need AI Tutors
&lt;/h2&gt;

&lt;p&gt;Private tutoring in India costs Rs 2,000-10,000 per month per subject. For a family earning Rs 30,000/month, that is 25-70% of household income. 80% of Indian students cannot afford quality tutoring.&lt;/p&gt;

&lt;p&gt;An AI tutor like &lt;a href="https://easelearn.ai/ai-tutor" rel="noopener noreferrer"&gt;EaseLearn AI&lt;/a&gt; costs Rs 199/month for ALL subjects — or free for basic features. It is available 24/7, adapts to your pace, and works in Hindi and English.&lt;/p&gt;

&lt;p&gt;As covered by &lt;a href="https://aninews.in/news/business/indias-first-ai-classroom-that-refuses-to-move-on-until-you-understand-and-its-free20260420160950/" rel="noopener noreferrer"&gt;ANI News&lt;/a&gt; and &lt;a href="https://www.tribuneindia.com/news/business/indias-first-ai-classroom-that-refuses-to-move-on-until-you-understand-and-its-free/" rel="noopener noreferrer"&gt;Tribune India&lt;/a&gt;, EaseLearn AI has crossed 100,000 users with zero marketing spend — entirely through word of mouth.&lt;/p&gt;

&lt;h2&gt;
  
  
  Best AI Tutors for Indian Students in 2026
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. EaseLearn AI — Best Overall AI Tutor
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://easelearn.ai/ai-tutor" rel="noopener noreferrer"&gt;EaseLearn AI&lt;/a&gt; is purpose-built for Indian students preparing for JEE, NEET, CBSE, ICSE, and state board exams.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key features:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Camera-based &lt;a href="https://easelearn.ai/doubt-solver" rel="noopener noreferrer"&gt;doubt solver&lt;/a&gt; — point at any question, get step-by-step solutions in 3 seconds&lt;/li&gt;
&lt;li&gt;AI tutor that adapts explanations to your level&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://easelearn.ai/math-solver" rel="noopener noreferrer"&gt;Math solver&lt;/a&gt; for Class 6 to JEE Advanced&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://easelearn.ai/immersive-classroom" rel="noopener noreferrer"&gt;Immersive Classroom&lt;/a&gt; — AI generates live slides with diagrams while teaching (patent-pending)&lt;/li&gt;
&lt;li&gt;PYQ solver with pattern analysis&lt;/li&gt;
&lt;li&gt;Quiz generator with adaptive difficulty&lt;/li&gt;
&lt;li&gt;Hindi and English support&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Price:&lt;/strong&gt; Free tier (5 doubts/day) | &lt;a href="https://easelearn.ai/pricing" rel="noopener noreferrer"&gt;Premium Rs 199/month&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;Users:&lt;/strong&gt; 100,000+&lt;br&gt;
&lt;strong&gt;Backed by:&lt;/strong&gt; AWS, Google, Anthropic, NVIDIA, MeitY&lt;/p&gt;

&lt;h3&gt;
  
  
  2. ChatGPT — Best for General Questions
&lt;/h3&gt;

&lt;p&gt;ChatGPT is powerful but not built for Indian exam preparation. It does not know the difference between CBSE and JEE level physics. No camera input. No structured step-by-step solutions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Price:&lt;/strong&gt; Free / $20/month (Rs 1,700)&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Khan Academy Khanmigo — Best International Option
&lt;/h3&gt;

&lt;p&gt;Khanmigo is Khan Academy's AI tutor powered by OpenAI. Good for conceptual learning but not optimized for Indian curriculum. Not available in Hindi. Expensive at $44/month.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI Tutor vs Human Tutor — Honest Comparison
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;AI Tutor (EaseLearn)&lt;/th&gt;
&lt;th&gt;Human Tutor&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Available 24/7&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No (fixed hours)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Adapts to your pace&lt;/td&gt;
&lt;td&gt;Fully adaptive&lt;/td&gt;
&lt;td&gt;Depends on tutor&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;All subjects&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Usually 1-2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Doubt resolution time&lt;/td&gt;
&lt;td&gt;3 seconds&lt;/td&gt;
&lt;td&gt;Next session&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Visual lessons&lt;/td&gt;
&lt;td&gt;Live AI-generated&lt;/td&gt;
&lt;td&gt;Whiteboard&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost per month&lt;/td&gt;
&lt;td&gt;Rs 0-199&lt;/td&gt;
&lt;td&gt;Rs 2,000-10,000/subject&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hindi support&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Depends&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Camera input&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  How to Use an AI Tutor Effectively
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Start with diagnostic testing&lt;/strong&gt; — Use the AI tutor's quiz generator to find your weak chapters before studying&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use camera doubt solving&lt;/strong&gt; — When stuck on a problem, snap a photo instead of skipping it&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ask follow-up questions&lt;/strong&gt; — If you do not understand a solution, ask "explain differently" or "why this formula?"&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Practice with AI quizzes&lt;/strong&gt; — Adaptive quizzes find gaps you did not know existed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use Immersive Classroom for visual topics&lt;/strong&gt; — Organic chemistry mechanisms, physics diagrams, biology processes&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Frequently Asked Questions About AI Tutors
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Q: Can an AI tutor really replace a human tutor?&lt;/strong&gt;&lt;br&gt;
A: For 80% of daily study needs — yes. AI handles doubt solving, concept explanation, and practice better than most tutors. Human tutors still win for motivation and complex problem-solving discussions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: Is EaseLearn AI really free?&lt;/strong&gt;&lt;br&gt;
A: Yes. The free tier gives 5 doubt solutions per day. &lt;a href="https://easelearn.ai/pricing" rel="noopener noreferrer"&gt;Premium is Rs 199/month&lt;/a&gt; for unlimited access.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: Which AI tutor is best for JEE preparation?&lt;/strong&gt;&lt;br&gt;
A: &lt;a href="https://easelearn.ai/ai-tutor" rel="noopener noreferrer"&gt;EaseLearn AI&lt;/a&gt; — it knows JEE-specific methods, has PYQ analysis, and the camera doubt solver works for handwritten HC Verma problems.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Q: Do AI tutors work in Hindi?&lt;/strong&gt;&lt;br&gt;
A: EaseLearn AI works in Hindi and English. Most other AI tutors (ChatGPT, Khanmigo) are English-first.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;&lt;a href="https://easelearn.ai" rel="noopener noreferrer"&gt;EaseLearn AI&lt;/a&gt; — India's #1 free AI tutor. &lt;a href="https://easelearn.ai/doubt-solver" rel="noopener noreferrer"&gt;Doubt solver&lt;/a&gt; . &lt;a href="https://easelearn.ai/ai-tutor" rel="noopener noreferrer"&gt;AI tutor&lt;/a&gt; . &lt;a href="https://easelearn.ai/math-solver" rel="noopener noreferrer"&gt;Math solver&lt;/a&gt; . &lt;a href="https://easelearn.ai/immersive-classroom" rel="noopener noreferrer"&gt;Immersive Classroom&lt;/a&gt;. Featured in &lt;a href="https://aninews.in/news/business/indias-first-ai-classroom-that-refuses-to-move-on-until-you-understand-and-its-free20260420160950/" rel="noopener noreferrer"&gt;ANI News&lt;/a&gt; and &lt;a href="https://www.tribuneindia.com/news/business/indias-first-ai-classroom-that-refuses-to-move-on-until-you-understand-and-its-free/" rel="noopener noreferrer"&gt;Tribune India&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>education</category>
      <category>india</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Getting Started with the ant CLI: Deploy Claude Agents</title>
      <dc:creator>Avinash Sangle</dc:creator>
      <pubDate>Wed, 22 Apr 2026 05:25:26 +0000</pubDate>
      <link>https://dev.to/aavisangle/getting-started-with-the-ant-cli-deploy-claude-agents-50ml</link>
      <guid>https://dev.to/aavisangle/getting-started-with-the-ant-cli-deploy-claude-agents-50ml</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;This article was originally published on &lt;a href="https://avinashsangle.com/blog/ant-cli-getting-started" rel="noopener noreferrer"&gt;avinashsangle.com&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The ant CLI is Anthropic's official command-line client for the Claude API, and it's the fastest way to create, configure, and manage cloud-hosted agents without writing application code. From install to a running managed agent in under 10 minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;ant CLI&lt;/strong&gt; is Anthropic's official Go-based CLI for the Claude API, launched April 2026. It manages agents, environments, and sessions from your terminal.&lt;/li&gt;
&lt;li&gt;Install on macOS with &lt;code&gt;brew install anthropics/tap/ant&lt;/code&gt;. Linux and Go installs are also supported.&lt;/li&gt;
&lt;li&gt;Define agents as &lt;strong&gt;YAML files&lt;/strong&gt;, check them into Git, and deploy through CI - full GitOps for your agent configs.&lt;/li&gt;
&lt;li&gt;Sessions cost &lt;strong&gt;$0.08/hour&lt;/strong&gt; (billed to the millisecond) plus standard Claude token rates. Idle time is free.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What Is the ant CLI?
&lt;/h2&gt;

&lt;p&gt;The ant CLI shipped alongside &lt;a href="https://avinashsangle.com/blog/claude-managed-agents" rel="noopener noreferrer"&gt;Claude Managed Agents&lt;/a&gt; on April 8, 2026, and it's built specifically for developers who want to create, configure, and run cloud-hosted agents without writing wrapper code. The &lt;a href="https://github.com/anthropics/anthropic-cli" rel="noopener noreferrer"&gt;GitHub repo&lt;/a&gt; already has over 300 stars in its first ten days.&lt;/p&gt;

&lt;p&gt;It follows a resource-based command structure: &lt;code&gt;ant [resource] &amp;lt;command&amp;gt; [flags...]&lt;/code&gt;. Think of it like &lt;code&gt;kubectl&lt;/code&gt; for Claude agents. You can pipe YAML into it, extract fields with GJSON transforms, and chain commands in shell scripts. If you've worked with any modern infrastructure CLI, the patterns will feel familiar.&lt;/p&gt;

&lt;p&gt;One thing to clarify early: the ant CLI and Claude Code solve different problems. Claude Code is your interactive coding assistant in the terminal - you talk to it, it writes code, and you pay through a subscription. The ant CLI is a programmatic API client for managing hosted agent infrastructure. You authenticate with an API key, and you're billed at standard API rates. I use both daily, and they complement each other well. Claude Code even understands how to shell out to &lt;code&gt;ant&lt;/code&gt; natively.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Install the ant CLI
&lt;/h2&gt;

&lt;p&gt;There are three installation paths depending on your platform. If you're on macOS, Homebrew is the fastest route.&lt;/p&gt;

&lt;h3&gt;
  
  
  macOS (Homebrew)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install from Anthropic's tap&lt;/span&gt;
brew &lt;span class="nb"&gt;install &lt;/span&gt;anthropics/tap/ant

&lt;span class="c"&gt;# Clear the macOS quarantine flag (required)&lt;/span&gt;
xattr &lt;span class="nt"&gt;-d&lt;/span&gt; com.apple.quarantine &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;brew &lt;span class="nt"&gt;--prefix&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;/bin/ant"&lt;/span&gt;

&lt;span class="c"&gt;# Verify&lt;/span&gt;
ant &lt;span class="nt"&gt;--version&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That quarantine step trips people up. macOS flags unsigned binaries downloaded by Homebrew, and without clearing it you'll get a "cannot be opened because the developer cannot be verified" error. It's a one-time thing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Linux / WSL (curl)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;VERSION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1.2.1
&lt;span class="nv"&gt;OS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;uname&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; | &lt;span class="nb"&gt;tr&lt;/span&gt; &lt;span class="s1"&gt;'[:upper:]'&lt;/span&gt; &lt;span class="s1"&gt;'[:lower:]'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;ARCH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;uname&lt;/span&gt; &lt;span class="nt"&gt;-m&lt;/span&gt; | &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'s/x86_64/amd64/'&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'s/aarch64/arm64/'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"https://github.com/anthropics/anthropic-cli/releases/download/v&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;VERSION&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/ant_&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;VERSION&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;_&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;OS&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;_&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;ARCH&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.tar.gz"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  | &lt;span class="nb"&gt;sudo tar&lt;/span&gt; &lt;span class="nt"&gt;-xz&lt;/span&gt; &lt;span class="nt"&gt;-C&lt;/span&gt; /usr/local/bin ant
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  From Source (Go 1.22+)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go &lt;span class="nb"&gt;install &lt;/span&gt;github.com/anthropics/anthropic-cli/cmd/ant@latest
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PATH&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;go &lt;span class="nb"&gt;env &lt;/span&gt;GOPATH&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;/bin"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Set Your API Key
&lt;/h3&gt;

&lt;p&gt;Once installed, set your Anthropic API key. The CLI reads it from the &lt;code&gt;ANTHROPIC_API_KEY&lt;/code&gt; environment variable:&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="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ANTHROPIC_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"sk-ant-your-key-here"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can generate an API key from the &lt;a href="https://console.anthropic.com/settings/keys" rel="noopener noreferrer"&gt;Anthropic Console&lt;/a&gt;. I keep mine in a &lt;code&gt;.env&lt;/code&gt; file that my shell sources on startup, but any secret management approach works.&lt;/p&gt;

&lt;h3&gt;
  
  
  Shell Completions
&lt;/h3&gt;

&lt;p&gt;The ant CLI supports completions for bash, zsh, fish, and PowerShell. For zsh (the default macOS shell):&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="c"&gt;# Generate zsh completions&lt;/span&gt;
ant completion zsh &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; ~/.zfunc/_ant

&lt;span class="c"&gt;# Add to your .zshrc if not already there&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'fpath=(~/.zfunc $fpath)'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; ~/.zshrc
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'autoload -Uz compinit &amp;amp;&amp;amp; compinit'&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; ~/.zshrc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tab completion saves a lot of time when working with the &lt;code&gt;beta:&lt;/code&gt; namespaced commands, which can get long.&lt;/p&gt;

&lt;h2&gt;
  
  
  Core Concepts - Agents, Environments, and Sessions
&lt;/h2&gt;

&lt;p&gt;Before you create anything, it helps to understand how the four core pieces fit together.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Agent&lt;/strong&gt; - A versioned configuration defining the model, system prompt, tools, and MCP server connections. Think of it as a blueprint. Each update creates a new version, so you can roll back if needed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Environment&lt;/strong&gt; - A container template specifying pre-installed packages (pip, npm) and networking rules. Create it once, reference it by ID. Multiple sessions can share one environment config, but each gets its own isolated container.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Session&lt;/strong&gt; - A running instance that pairs an agent with an environment. It has its own container, filesystem, and conversation history. Sessions are where the actual work happens.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Events&lt;/strong&gt; - The communication protocol. You send user events (messages, interrupts, tool confirmations) and receive agent events (messages, tool calls, thinking). Everything is event-based and streamable.&lt;/p&gt;

&lt;p&gt;The flow works like this: you create an agent (the what), create an environment (the where), start a session linking them together, and then communicate through events. Anthropic handles the container orchestration, tool execution, and conversation state. According to the &lt;a href="https://platform.claude.com/docs/en/managed-agents/overview" rel="noopener noreferrer"&gt;official docs&lt;/a&gt;, sessions cost $0.08 per session-hour billed to the millisecond, and idle time doesn't count.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating Your First Agent with the ant CLI
&lt;/h2&gt;

&lt;p&gt;Let's build a simple code review agent. I'll walk through each step so you can see exactly what the CLI does at each stage. All managed agent commands sit under the &lt;code&gt;beta:&lt;/code&gt; prefix since the feature is still in beta.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Create the Agent
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ant beta:agents create &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"Code Reviewer"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--model&lt;/span&gt; claude-sonnet-4-6 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--system&lt;/span&gt; &lt;span class="s2"&gt;"You are a senior code reviewer. Read the code carefully, check for bugs, security issues, and style problems. Be specific about line numbers and provide fix suggestions."&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--tool&lt;/span&gt; &lt;span class="s1"&gt;'{"type": "agent_toolset_20260401"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response comes back as JSON with the agent ID and version. I like to extract just the ID for scripting:&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="c"&gt;# Extract the agent ID&lt;/span&gt;
&lt;span class="nv"&gt;AGENT_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;ant beta:agents create &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"Code Reviewer"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--model&lt;/span&gt; claude-sonnet-4-6 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--system&lt;/span&gt; &lt;span class="s2"&gt;"You are a senior code reviewer."&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--tool&lt;/span&gt; &lt;span class="s1"&gt;'{"type": "agent_toolset_20260401"}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--transform&lt;/span&gt; &lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="nt"&gt;--format&lt;/span&gt; raw&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Created agent: &lt;/span&gt;&lt;span class="nv"&gt;$AGENT_ID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;--transform&lt;/code&gt; flag uses GJSON syntax to pluck a specific field from the response, and &lt;code&gt;--format raw&lt;/code&gt; strips the quotes. This is one of the CLI's best features for scripting.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Create an Environment
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;ENV_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;ant beta:environments create &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="s2"&gt;"python-dev"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--pip-packages&lt;/span&gt; &lt;span class="s1"&gt;'["pytest", "ruff", "mypy"]'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--networking&lt;/span&gt; unrestricted &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--transform&lt;/span&gt; &lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="nt"&gt;--format&lt;/span&gt; raw&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Created environment: &lt;/span&gt;&lt;span class="nv"&gt;$ENV_ID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Environments define what's pre-installed in the container. I'm giving this one Python linting tools since it's a code review agent. The &lt;code&gt;unrestricted&lt;/code&gt; networking flag lets the agent fetch external resources if needed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Start a Session
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;SESSION_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;ant beta:sessions create &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--agent-id&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$AGENT_ID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--environment-id&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ENV_ID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--transform&lt;/span&gt; &lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="nt"&gt;--format&lt;/span&gt; raw&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Started session: &lt;/span&gt;&lt;span class="nv"&gt;$SESSION_ID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 4: Send a Message and Stream the Response
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Send a review request&lt;/span&gt;
ant beta:sessions:events send &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--session-id&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SESSION_ID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--type&lt;/span&gt; user.message &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--content-type&lt;/span&gt; text &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--content-text&lt;/span&gt; &lt;span class="s2"&gt;"Review this Python function for bugs:

def divide(a, b):
    return a / b
"&lt;/span&gt;

&lt;span class="c"&gt;# Stream the agent's response in real-time&lt;/span&gt;
ant beta:sessions stream &lt;span class="nt"&gt;--session-id&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SESSION_ID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;stream&lt;/code&gt; command opens a real-time SSE connection to the session. You'll see the agent's thinking, tool calls (it might run the code through ruff), and its final review - all printed to your terminal as they happen.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Tip:&lt;/strong&gt; Want to explore the response interactively? Replace &lt;code&gt;--format raw&lt;/code&gt; with &lt;code&gt;--format explore&lt;/code&gt; on any command to open the TUI explorer. It lets you navigate nested JSON with arrow keys - really useful when debugging agent responses.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  YAML Version Control for Agents
&lt;/h2&gt;

&lt;p&gt;This is the ant CLI's best feature, and the one I haven't seen anyone write about yet. Instead of passing flags inline, you can define agents and environments as YAML files, check them into Git, and deploy through your CI pipeline.&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="c1"&gt;# code-reviewer.agent.yaml&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;Code Reviewer&lt;/span&gt;
&lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;claude-sonnet-4-6&lt;/span&gt;
&lt;span class="na"&gt;system&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
  &lt;span class="s"&gt;You are a senior code reviewer. Read the code carefully,&lt;/span&gt;
  &lt;span class="s"&gt;check for bugs, security issues, and style problems.&lt;/span&gt;
  &lt;span class="s"&gt;Be specific about line numbers and provide fix suggestions.&lt;/span&gt;
&lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;agent_toolset_20260401&lt;/span&gt;
    &lt;span class="na"&gt;configs&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_fetch&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;false&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="c1"&gt;# code-reviewer.environment.yaml&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;python-dev&lt;/span&gt;
&lt;span class="na"&gt;pip_packages&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;pytest&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ruff&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;mypy&lt;/span&gt;
&lt;span class="na"&gt;networking&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;unrestricted&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you can create the agent directly from the file:&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="c"&gt;# Create from YAML&lt;/span&gt;
ant beta:agents create &amp;lt; code-reviewer.agent.yaml

&lt;span class="c"&gt;# Update an existing agent (version is required for safety)&lt;/span&gt;
ant beta:agents update &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--agent-id&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$AGENT_ID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--version&lt;/span&gt; 1 &lt;span class="se"&gt;\&lt;/span&gt;
  &amp;lt; code-reviewer.agent.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The versioning requirement matters. When you update an agent, you must pass the current version number. If someone else updated it since you last pulled, the command fails rather than silently overwriting. It's optimistic concurrency control - the same pattern you'd find in Kubernetes or Terraform.&lt;/p&gt;

&lt;p&gt;This YAML approach is where the ant CLI really shines for teams. Your agent configs live in the same repo as your application code, go through pull request review, and deploy through the same pipeline. I wrote more about the broader Managed Agents architecture in my &lt;a href="https://avinashsangle.com/blog/claude-managed-agents" rel="noopener noreferrer"&gt;Managed Agents vs Agent SDK comparison&lt;/a&gt;, but the YAML workflow is what makes the CLI my preferred interface.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;According to the &lt;a href="https://platform.claude.com/docs/en/api/sdks/cli" rel="noopener noreferrer"&gt;official CLI docs&lt;/a&gt;, Anthropic designed the YAML workflow specifically for GitOps-style agent management. If you're already doing infrastructure as code, this slots right in.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  ant CLI vs curl vs SDK - Why Use the CLI?
&lt;/h2&gt;

&lt;p&gt;You can hit the Managed Agents API three ways: raw HTTP with curl, a language SDK (Python, TypeScript, Go, etc.), or the ant CLI. Each has its place.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;curl&lt;/th&gt;
&lt;th&gt;ant CLI&lt;/th&gt;
&lt;th&gt;Python SDK&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Setup time&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;2 minutes&lt;/td&gt;
&lt;td&gt;5 minutes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;JSON body authoring&lt;/td&gt;
&lt;td&gt;Manual&lt;/td&gt;
&lt;td&gt;Typed flags / YAML&lt;/td&gt;
&lt;td&gt;Typed objects&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Auto-pagination&lt;/td&gt;
&lt;td&gt;Manual&lt;/td&gt;
&lt;td&gt;Built-in&lt;/td&gt;
&lt;td&gt;Built-in&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;File references&lt;/td&gt;
&lt;td&gt;Manual base64&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;@path&lt;/code&gt; syntax&lt;/td&gt;
&lt;td&gt;File objects&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Response filtering&lt;/td&gt;
&lt;td&gt;Pipe to jq&lt;/td&gt;
&lt;td&gt;&lt;code&gt;--transform&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Code&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Shell scripting&lt;/td&gt;
&lt;td&gt;Verbose&lt;/td&gt;
&lt;td&gt;Ergonomic&lt;/td&gt;
&lt;td&gt;Requires Python&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CI/CD fit&lt;/td&gt;
&lt;td&gt;OK&lt;/td&gt;
&lt;td&gt;Excellent&lt;/td&gt;
&lt;td&gt;Good&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Best for&lt;/td&gt;
&lt;td&gt;Quick tests&lt;/td&gt;
&lt;td&gt;Ops / automation&lt;/td&gt;
&lt;td&gt;App integration&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The ant CLI sits in a sweet spot. It's faster than writing curl commands by hand (no JSON body construction, no header management), and lighter than pulling in a full SDK when you just want to script some agent operations. For anything that lives in a shell script or CI workflow, it's the right tool.&lt;/p&gt;

&lt;p&gt;If you're building an application that embeds agent interactions - a web app, a Slack bot, a data pipeline - use the SDK. The ant CLI is for the operational layer: provisioning agents, rotating credentials, monitoring sessions, deploying config changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scripting and Automation Patterns
&lt;/h2&gt;

&lt;p&gt;Here are a few patterns I've found useful when automating agent workflows with the ant CLI.&lt;/p&gt;

&lt;h3&gt;
  
  
  Extract IDs from Create Commands
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-euo&lt;/span&gt; pipefail

&lt;span class="c"&gt;# Create agent and capture the ID&lt;/span&gt;
&lt;span class="nv"&gt;AGENT_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;ant beta:agents create &lt;span class="se"&gt;\&lt;/span&gt;
  &amp;lt; agents/reviewer.agent.yaml &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--transform&lt;/span&gt; &lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="nt"&gt;--format&lt;/span&gt; raw&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# Create environment and capture the ID&lt;/span&gt;
&lt;span class="nv"&gt;ENV_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;ant beta:environments create &lt;span class="se"&gt;\&lt;/span&gt;
  &amp;lt; agents/reviewer.environment.yaml &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--transform&lt;/span&gt; &lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="nt"&gt;--format&lt;/span&gt; raw&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Agent: &lt;/span&gt;&lt;span class="nv"&gt;$AGENT_ID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Environment: &lt;/span&gt;&lt;span class="nv"&gt;$ENV_ID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# Store for later use&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"AGENT_ID=&lt;/span&gt;&lt;span class="nv"&gt;$AGENT_ID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; .env.agents
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"ENV_ID=&lt;/span&gt;&lt;span class="nv"&gt;$ENV_ID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; .env.agents
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  GitHub Actions Deployment
&lt;/h3&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;Deploy Agents&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;main&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;agents/**'&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&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;Install ant CLI&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;curl -fsSL \&lt;/span&gt;
            &lt;span class="s"&gt;"https://github.com/anthropics/anthropic-cli/releases/download/v1.2.1/ant_1.2.1_linux_amd64.tar.gz" \&lt;/span&gt;
            &lt;span class="s"&gt;| sudo tar -xz -C /usr/local/bin ant&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;Update agent config&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;ANTHROPIC_API_KEY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.ANTHROPIC_API_KEY }}&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;ant beta:agents update \&lt;/span&gt;
            &lt;span class="s"&gt;--agent-id "${{ vars.AGENT_ID }}" \&lt;/span&gt;
            &lt;span class="s"&gt;--version "${{ vars.AGENT_VERSION }}" \&lt;/span&gt;
            &lt;span class="s"&gt;&amp;lt; agents/reviewer.agent.yaml&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  List All Agents and Environments
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# List agents in a readable table&lt;/span&gt;
ant beta:agents list &lt;span class="nt"&gt;--format&lt;/span&gt; yaml

&lt;span class="c"&gt;# List environments with just names and IDs&lt;/span&gt;
ant beta:environments list &lt;span class="nt"&gt;--transform&lt;/span&gt; &lt;span class="s2"&gt;"data.#.{id,name}"&lt;/span&gt; &lt;span class="nt"&gt;--format&lt;/span&gt; yaml

&lt;span class="c"&gt;# Check session status&lt;/span&gt;
ant beta:sessions retrieve &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--session-id&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SESSION_ID&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--transform&lt;/span&gt; status &lt;span class="nt"&gt;--format&lt;/span&gt; raw
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;--transform&lt;/code&gt; flag accepts full GJSON path syntax. You can filter arrays, project specific fields, and even do conditional extraction. It's much cleaner than piping to &lt;code&gt;jq&lt;/code&gt; for simple extractions, though for complex transformations I still reach for jq.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Tools Can Managed Agents Use?
&lt;/h2&gt;

&lt;p&gt;When you include &lt;code&gt;{"type": "agent_toolset_20260401"}&lt;/code&gt; in your agent config, it gets access to a standard set of tools: bash, read, write, edit, glob, grep, and web_fetch. All are enabled by default.&lt;/p&gt;

&lt;p&gt;You can selectively disable tools you don't want the agent to have. For a read-only code review agent, you might disable write and edit:&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="c1"&gt;# readonly-reviewer.agent.yaml&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;Read-Only Reviewer&lt;/span&gt;
&lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;claude-sonnet-4-6&lt;/span&gt;
&lt;span class="na"&gt;system&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Review code without modifying it.&lt;/span&gt;
&lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;agent_toolset_20260401&lt;/span&gt;
    &lt;span class="na"&gt;configs&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;write&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;false&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;edit&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;false&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_fetch&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;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or flip the default and whitelist only what you need:&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;tools&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;agent_toolset_20260401&lt;/span&gt;
    &lt;span class="na"&gt;default_config&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;false&lt;/span&gt;
    &lt;span class="na"&gt;configs&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;bash&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="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;read&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Agents can also connect to external MCP servers for tools beyond the built-in set. If you've built a custom MCP server, a managed agent can use it by adding an &lt;code&gt;mcp_servers&lt;/code&gt; block to the agent config.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently Asked Questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is the ant CLI from Anthropic?
&lt;/h3&gt;

&lt;p&gt;The ant CLI is Anthropic's official command-line client for the Claude API. Written in Go, it provides a resource-based command structure for managing agents, environments, and sessions. It supports typed flags, YAML input, auto-pagination, and multiple output formats including an interactive TUI explorer.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I install the ant CLI on macOS?
&lt;/h3&gt;

&lt;p&gt;Install via Homebrew: run &lt;code&gt;brew install anthropics/tap/ant&lt;/code&gt;, then clear the macOS quarantine flag with &lt;code&gt;xattr -d com.apple.quarantine "$(brew --prefix)/bin/ant"&lt;/code&gt;. Set your &lt;code&gt;ANTHROPIC_API_KEY&lt;/code&gt; environment variable and verify with &lt;code&gt;ant --version&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the difference between the ant CLI and Claude Code?
&lt;/h3&gt;

&lt;p&gt;Claude Code is an interactive agentic coding assistant that runs in your terminal and uses a subscription. The ant CLI is a programmatic API client for managing Managed Agents resources, uses an API key, and is built for scripting and CI/CD automation. They're complementary - Claude Code can even shell out to &lt;code&gt;ant&lt;/code&gt; commands.&lt;/p&gt;

&lt;h3&gt;
  
  
  How much does it cost to run a managed agent session?
&lt;/h3&gt;

&lt;p&gt;Sessions cost $0.08 per session-hour, billed to the millisecond. Idle time is free. You also pay standard Claude API token rates on top. A typical 1-hour coding session with Opus costs roughly $0.70 total including both tokens and session runtime.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I version control agents with the ant CLI?
&lt;/h3&gt;

&lt;p&gt;Yes. Define agents as YAML files (e.g. &lt;code&gt;reviewer.agent.yaml&lt;/code&gt;), check them into Git, and deploy via CI. Use &lt;code&gt;ant beta:agents create&lt;/code&gt; to create from YAML and &lt;code&gt;ant beta:agents update&lt;/code&gt; with the version flag to push updates. This gives you full GitOps for agent configurations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can managed agents connect to MCP servers?
&lt;/h3&gt;

&lt;p&gt;Yes. Agents support remote MCP server connections via the &lt;code&gt;--mcp-server&lt;/code&gt; flag. You specify the server URL and name, then add an &lt;code&gt;mcp_toolset&lt;/code&gt; tool entry referencing that server. This lets agents use tools from GitHub, Slack, or custom MCP servers you've built.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I use the ant CLI in CI/CD pipelines?
&lt;/h3&gt;

&lt;p&gt;Define agents and environments as YAML files in your repo. In CI, use &lt;code&gt;ant beta:agents create &amp;lt; agent.yaml&lt;/code&gt; to provision and &lt;code&gt;ant beta:agents update&lt;/code&gt; to deploy changes. The &lt;code&gt;--transform&lt;/code&gt; flag extracts IDs for scripting, and &lt;code&gt;--format&lt;/code&gt; controls output parsing.&lt;/p&gt;

&lt;h3&gt;
  
  
  What tools are available to managed agents?
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;agent_toolset_20260401&lt;/code&gt; built-in toolset includes bash, read, write, edit, glob, grep, and web_fetch. You can enable or disable individual tools, or disable all by default and whitelist specific ones. Agents can also connect to external MCP servers for custom tool integrations.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Read the full tutorial&lt;/strong&gt; with interactive code examples and component-based layout on the original post: &lt;a href="https://avinashsangle.com/blog/ant-cli-getting-started" rel="noopener noreferrer"&gt;Getting Started with the ant CLI on avinashsangle.com&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>claudecode</category>
      <category>ai</category>
      <category>devops</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Wrapper Classes in Java – A Simple Guide</title>
      <dc:creator>Jayashree</dc:creator>
      <pubDate>Wed, 22 Apr 2026 05:01:29 +0000</pubDate>
      <link>https://dev.to/jayashree_a84b6eff7bc414e/wrapper-classes-in-java-a-simple-guide-4gpm</link>
      <guid>https://dev.to/jayashree_a84b6eff7bc414e/wrapper-classes-in-java-a-simple-guide-4gpm</guid>
      <description>&lt;p&gt;When we start learning Java, we mostly work with primitive data types like &lt;strong&gt;int, char, double, etc&lt;/strong&gt;. These are fast and efficient. But at some point, we run into situations where primitives alone are not enough. That’s where &lt;strong&gt;wrapper classes&lt;/strong&gt; come into the picture.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is a Wrapper Class?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;wrapper class&lt;/strong&gt; is used to &lt;strong&gt;convert a primitive data type into an object&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In simple terms, it “wraps” a primitive value inside an object.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For example:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;int → Integer&lt;/li&gt;
&lt;li&gt;char → Character&lt;/li&gt;
&lt;li&gt;double → Double&lt;/li&gt;
&lt;li&gt;boolean → Boolean&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So instead of working with raw values, we can work with objects.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why Do We Need Wrapper Classes?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You might wonder — &lt;strong&gt;why not just use primitives?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Good question. Here are the main reasons:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Collections work only with objects&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Java collections like ArrayList cannot store primitive types.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;ArrayList&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;list&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;//  Not allowed&lt;/span&gt;
&lt;span class="nc"&gt;ArrayList&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Integer&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;list&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;//  Allowed&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So we use wrapper classes to store values in collections.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Utility Methods&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Wrapper classes provide useful methods like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;Integer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;parseInt&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"123"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// converts String to int&lt;/span&gt;
&lt;span class="nc"&gt;Double&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;valueOf&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"10.5"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;    &lt;span class="c1"&gt;// converts String to Double&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These methods make data conversion easy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Object-Oriented Features&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Sometimes we need objects instead of primitives—for example, when working with APIs, frameworks, or generics.&lt;/p&gt;

&lt;p&gt;Wrapper classes help us follow object-oriented programming concepts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Autoboxing and Unboxing&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Java automatically converts between primitives and wrapper classes.&lt;/p&gt;

&lt;p&gt;Autoboxing (primitive → object)&lt;br&gt;
Integer num = 10;  // int → Integer&lt;/p&gt;

&lt;p&gt;Unboxing (object → primitive)&lt;br&gt;
Integer num = 10;&lt;br&gt;
int value = num;   // Integer → int&lt;/p&gt;

&lt;p&gt;This makes coding easier and cleaner.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where Are Wrapper Objects Stored?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Primitive&lt;/strong&gt; &lt;strong&gt;values&lt;/strong&gt; → stored in &lt;code&gt;stack memory&lt;/code&gt;&lt;br&gt;
&lt;strong&gt;Wrapper objects&lt;/strong&gt; → stored in &lt;code&gt;heap memory&lt;/code&gt;&lt;br&gt;
&lt;strong&gt;Reference variables&lt;/strong&gt; → stored in &lt;code&gt;stack&lt;/code&gt;&lt;/p&gt;

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

&lt;p&gt;Integer a = 10;&lt;/p&gt;

&lt;p&gt;Here:&lt;/p&gt;

&lt;p&gt;a is stored in stack&lt;br&gt;
The actual object (10) is stored in heap&lt;br&gt;
Special Case: Integer Caching&lt;/p&gt;

&lt;p&gt;Java caches values between -128 to 127.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;Integer&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="nc"&gt;Integer&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because Java reuses the same object from memory.&lt;/p&gt;

&lt;p&gt;But:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;Integer&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="nc"&gt;Integer&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, new objects are created.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Final Thoughts&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Wrapper classes play a very important role in Java. Even though primitives are faster, wrapper classes give flexibility, especially when working with collections, APIs, and object-based operations.&lt;/p&gt;

&lt;p&gt;If you are preparing for interviews, understanding wrapper classes, autoboxing, and memory behavior is a must.&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>java</category>
      <category>programming</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How I Saved a Mumbai CA Firm ₹18 Lakh/Year by Automating GST Invoice Reconciliation</title>
      <dc:creator>Archit Mittal</dc:creator>
      <pubDate>Wed, 22 Apr 2026 04:57:08 +0000</pubDate>
      <link>https://dev.to/automate-archit/how-i-saved-a-mumbai-ca-firm-18-lakhyear-by-automating-gst-invoice-reconciliation-3fdj</link>
      <guid>https://dev.to/automate-archit/how-i-saved-a-mumbai-ca-firm-18-lakhyear-by-automating-gst-invoice-reconciliation-3fdj</guid>
      <description>&lt;p&gt;Last month, I worked with a mid-sized CA firm in Andheri, Mumbai. They had 4 junior accountants whose full-time job was reconciling GSTR-2B data with purchase invoices for 80+ clients. Each reconciliation took 6-8 hours per client per month.&lt;/p&gt;

&lt;p&gt;The math was brutal: 4 accountants × ₹35,000/month × 12 months = ₹16.8 lakh/year in salaries, plus ₹1.2 lakh in infrastructure and overhead. Total: ₹18 lakh/year of manual work that a 200-line Python script could replace.&lt;/p&gt;

&lt;p&gt;Here is exactly how I did it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;Every month, Indian businesses running on GST deal with two data sources:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;GSTR-2B JSON&lt;/strong&gt; from the GST portal — purchases reported by your suppliers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Purchase register&lt;/strong&gt; exported from Tally, Zoho Books, Busy, or plain Excel.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;An accountant then matches invoice numbers, GSTIN, taxable values, and tax amounts row by row. Mismatches — wrong GSTIN, missing invoices, ITC eligibility flags — get flagged manually. It is tedious, error-prone, and scales linearly with client count.&lt;/p&gt;

&lt;p&gt;The firm's owner told me plainly: "We quote ₹5,000/client for monthly reconciliation. We spend ₹8,000 worth of accountant hours on it. Every new client makes us less profitable."&lt;/p&gt;

&lt;p&gt;That was the unit economics problem I needed to fix.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution: A 3-Part Python Pipeline
&lt;/h2&gt;

&lt;p&gt;I built a three-step pipeline that runs in about 4 minutes per client instead of 8 hours.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Parse GSTR-2B JSON
&lt;/h3&gt;

&lt;p&gt;The JSON from the GST portal is nested. Flatten it into a reconciliation-ready DataFrame:&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;json&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;parse_gstr2b&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json_path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Flatten GSTR-2B JSON into a reconciliation-ready DataFrame.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;json_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;utf-8&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="n"&gt;b2b_section&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&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="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;data&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="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;docdata&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="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;b2b&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="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;supplier&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;b2b_section&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;gstin&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;supplier&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="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ctin&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="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;supplier_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;supplier&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="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;trdnm&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="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;invoice&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;supplier&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="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;inv&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="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;supplier_gstin&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;gstin&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;supplier_name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;supplier_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;invoice_no&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;invoice&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="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;inum&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="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;upper&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;invoice_date&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;invoice&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="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;dt&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="p"&gt;),&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;taxable_value&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;invoice&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="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;val&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;igst&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;invoice&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="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;iamt&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;cgst&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;invoice&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="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;camt&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;sgst&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;invoice&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="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;samt&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;itc_eligible&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;invoice&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="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;itcavl&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;Y&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Y&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="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Normalize the Purchase Register
&lt;/h3&gt;

&lt;p&gt;Client data comes in 15 different formats. Column names are inconsistent across Tally, Zoho, Busy, and handcrafted Excel sheets. I normalize everything to the same schema before reconciling:&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;load_purchase_register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Handle Tally exports, Zoho CSVs, and client Excel files.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;suffix&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.csv&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_excel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sheet_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Column aliases seen across 80+ clients
&lt;/span&gt;    &lt;span class="n"&gt;alias_map&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;GSTIN&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;supplier_gstin&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;Party GSTIN&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;supplier_gstin&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;Vendor GSTIN&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;supplier_gstin&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;Invoice Number&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;invoice_no&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;Bill No&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;invoice_no&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;Bill Number&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;invoice_no&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;Taxable Amount&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;taxable_value&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;Basic Amount&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;taxable_value&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;IGST Amount&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;igst&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;CGST Amount&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;cgst&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;SGST Amount&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;sgst&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="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rename&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;columns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;alias_map&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;columns&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;invoice_no&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;invoice_no&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;astype&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;upper&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;numeric_cols&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;taxable_value&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;igst&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;cgst&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;sgst&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;col&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;numeric_cols&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;col&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;columns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;col&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_numeric&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;col&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;coerce&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fillna&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&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;df&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: Reconcile and Flag Mismatches
&lt;/h3&gt;

&lt;p&gt;This is the heart of the script. Outer-join both datasets on GSTIN and invoice number, then tag every row with a reconciliation status:&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;reconcile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gstr2b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;purchase&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tolerance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Match on GSTIN + invoice_no. Flag tax differences above tolerance.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;merged&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;gstr2b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;purchase&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;on&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;supplier_gstin&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;invoice_no&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;how&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;outer&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;suffixes&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;_2b&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;_book&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;indicator&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;classify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;_merge&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;left_only&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;MISSING_IN_BOOKS&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;row&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;_merge&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;right_only&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;MISSING_IN_2B&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;

        &lt;span class="n"&gt;diff_igst&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;row&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="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;igst_2b&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;row&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="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;igst_book&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;diff_cgst&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;row&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="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;cgst_2b&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;row&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="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;cgst_book&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;diff_sgst&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;row&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="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;sgst_2b&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;row&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="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;sgst_book&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;diff_igst&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;diff_cgst&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;diff_sgst&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;tolerance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;TAX_MISMATCH&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;

        &lt;span class="n"&gt;diff_taxable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;abs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;row&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="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;taxable_value_2b&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;row&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="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;taxable_value_book&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;diff_taxable&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;tolerance&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;VALUE_MISMATCH&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;

        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;MATCHED&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;

    &lt;span class="n"&gt;merged&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;merged&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;apply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;classify&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&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;merged&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Orchestrator
&lt;/h3&gt;

&lt;p&gt;A small driver wraps everything into an Excel report the accountant can email to the client:&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run_reconciliation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client_code&lt;/span&gt;&lt;span class="p"&gt;:&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;month&lt;/span&gt;&lt;span class="p"&gt;:&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;base&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Path&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;/clients/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;client_code&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;month&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;gstr2b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parse_gstr2b&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;gstr2b.json&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;purchase&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;load_purchase_register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;purchase_register.xlsx&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;reconcile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;gstr2b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;purchase&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;summary&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;value_counts&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;to_dict&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;output_path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;base&lt;/span&gt; &lt;span class="o"&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;reco_&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;client_code&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;_&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;month&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.xlsx&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;

    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ExcelWriter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;output_path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;engine&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;openpyxl&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_excel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sheet_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Full_Reconciliation&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;MISSING_IN_BOOKS&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;MISSING_IN_2B&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;TAX_MISMATCH&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
            &lt;span class="n"&gt;subset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="n"&gt;subset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_excel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sheet_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nf"&gt;print&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="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;client_code&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;month&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;summary&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="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;output_path&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Drop this into a cron job, point it at a folder structure like &lt;code&gt;/clients/ABC123/2026-03/&lt;/code&gt;, and you have a nightly reconciliation run for every client.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Results — 60 Days Later
&lt;/h2&gt;

&lt;p&gt;After running this pipeline across all 80 clients for two months:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Before&lt;/th&gt;
&lt;th&gt;After&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Hours per client / month&lt;/td&gt;
&lt;td&gt;7 hrs&lt;/td&gt;
&lt;td&gt;15 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Accountants needed&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;1 (review only)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Monthly labour cost&lt;/td&gt;
&lt;td&gt;₹1.4 lakh&lt;/td&gt;
&lt;td&gt;₹45,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Error rate&lt;/td&gt;
&lt;td&gt;4-6%&lt;/td&gt;
&lt;td&gt;under 0.5%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Client turnaround&lt;/td&gt;
&lt;td&gt;3-5 days&lt;/td&gt;
&lt;td&gt;Same day&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Three of the four junior accountants were not laid off — they were redeployed to higher-value advisory work (ITR-3 filings, tax planning, GST notice responses) at ₹60,000/month. A real upgrade for them, and higher revenue per head for the firm.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Annual savings: approximately ₹18 lakh.&lt;/strong&gt; Script cost: one weekend of my time, plus ₹0/month in infra (it runs on their existing laptop via a Windows Task Scheduler job).&lt;/p&gt;

&lt;h2&gt;
  
  
  Gotchas I Hit
&lt;/h2&gt;

&lt;p&gt;A few things that tripped me up the first time and are worth calling out:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Invoice number normalization is the hardest part.&lt;/strong&gt; Suppliers write "INV-001", "INV/001", "Inv 001", and "invoice 1" for the same bill. On top of the &lt;code&gt;.strip().upper()&lt;/code&gt; normalization, I added a &lt;code&gt;difflib.SequenceMatcher&lt;/code&gt; fuzzy-match layer with a 0.85 cutoff for leftover unmatched rows. It caught another 3% of matches that would otherwise have been false negatives.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rounding differences in CGST/SGST.&lt;/strong&gt; Tally and the GST portal occasionally round in opposite directions. A ₹0.50 tolerance on tax heads removes about 90% of false mismatches.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Credit notes live in a different JSON section.&lt;/strong&gt; They are under &lt;code&gt;cdnr&lt;/code&gt; (credit/debit notes for registered) inside GSTR-2B, not &lt;code&gt;b2b&lt;/code&gt;. Parse them separately and flip the sign.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ITC ineligibility flags under Rule 42/43&lt;/strong&gt; are not always set correctly by suppliers. The script flags these for human review rather than auto-accepting — human judgment still matters here.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Who Can Use This Today
&lt;/h2&gt;

&lt;p&gt;If you run or work at a CA firm, a tax consultancy, or any business with 20+ vendor invoices per month, this pipeline pays for itself in week one. The full production script is around 200 lines. Swap &lt;code&gt;pandas&lt;/code&gt; for &lt;code&gt;polars&lt;/code&gt; if you are processing 100,000+ rows per month.&lt;/p&gt;

&lt;p&gt;The pattern generalizes. Any reconciliation that compares two structured data sources is a 1-2 day Python project that can eliminate a full-time role:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Bank statements vs. books of accounts&lt;/li&gt;
&lt;li&gt;Payroll register vs. PF/ESI challans&lt;/li&gt;
&lt;li&gt;Sales register vs. e-way bills&lt;/li&gt;
&lt;li&gt;Shopify/Amazon settlement reports vs. GSTR-1&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;India has roughly 1.4 million CA firms and tax consultants. Most of them are still doing this by hand. The tooling gap is staggering — and the ROI on closing it is immediate.&lt;/p&gt;

&lt;p&gt;If you try this and run into trouble, the three places things usually break are: invoice number cleaning, JSON schema drift when the GSTN portal updates, and the &lt;code&gt;pd.read_excel&lt;/code&gt; engine choice for large files (use &lt;code&gt;openpyxl&lt;/code&gt; for &lt;code&gt;.xlsx&lt;/code&gt;, &lt;code&gt;xlrd==1.2.0&lt;/code&gt; for legacy &lt;code&gt;.xls&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Ship it on Monday. Save ₹1 lakh by Friday.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I'm Archit Mittal — I automate chaos for businesses. Follow me for daily automation content.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>automation</category>
      <category>tutorial</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Claude Code Routines: Put Your Laravel Workflows on Autopilot</title>
      <dc:creator>Hafiz</dc:creator>
      <pubDate>Wed, 22 Apr 2026 04:45:27 +0000</pubDate>
      <link>https://dev.to/hafiz619/claude-code-routines-put-your-laravel-workflows-on-autopilot-56hh</link>
      <guid>https://dev.to/hafiz619/claude-code-routines-put-your-laravel-workflows-on-autopilot-56hh</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Originally published at &lt;a href="https://hafiz.dev/blog/claude-code-routines-laravel-autopilot" rel="noopener noreferrer"&gt;hafiz.dev&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;The problem with most AI coding workflows is they stop when you do. You close your laptop, the session ends. You're asleep, nothing runs. You come back Monday morning to a pile of unreviewed PRs and no idea whether Friday's deploy is healthy.&lt;/p&gt;

&lt;p&gt;Claude Code Routines changes that. They run on Anthropic-managed cloud infrastructure, which means the session keeps going whether your machine is on or not. You write the prompt once, wire up a trigger, and the work happens in the background.&lt;/p&gt;

&lt;p&gt;This is a practical guide to getting Routines set up on a real Laravel project. Five concrete use cases, actual prompts, and the gotchas you'll run into before you do.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Routines are (and what they're not)
&lt;/h2&gt;

&lt;p&gt;A Routine is a saved Claude Code configuration: a prompt, one or more GitHub repositories, and optional MCP connectors, packaged together and triggered automatically. Each run creates a full Claude Code cloud session on Anthropic's infrastructure. Claude clones your repo, does the work, and pushes to a &lt;code&gt;claude/&lt;/code&gt;-prefixed branch.&lt;/p&gt;

&lt;p&gt;Three things are worth knowing upfront.&lt;/p&gt;

&lt;p&gt;First, Routines are different from &lt;code&gt;/loop&lt;/code&gt; and Desktop scheduled tasks. &lt;code&gt;/loop&lt;/code&gt; runs prompts inside your current terminal session and dies when you close it. Desktop scheduled tasks run on a schedule but need your machine to be on and Claude Code Desktop open. Routines are the only option that runs fully unattended on cloud infrastructure.&lt;/p&gt;

&lt;p&gt;Second, this feature is currently in research preview. Behavior, the API shapes, and rate limits may change. That said, it's available right now on Pro, Max, Team, and Enterprise plans with Claude Code on the web enabled at &lt;a href="https://claude.ai/code" rel="noopener noreferrer"&gt;claude.ai/code&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Third, Routines run fully autonomously. No approval prompts during a run. Whatever you write in the prompt is what runs. This is meaningfully different from a normal Claude Code session where you can steer mid-task. If you've used &lt;a href="https://hafiz.dev/blog/claude-code-channels-how-to-control-your-ai-agent-from-your-phone" rel="noopener noreferrer"&gt;Claude Code Channels&lt;/a&gt; to send commands remotely, think of Routines as the version that runs without you sending anything at all.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Option&lt;/th&gt;
&lt;th&gt;Where it runs&lt;/th&gt;
&lt;th&gt;Machine required?&lt;/th&gt;
&lt;th&gt;Survives closing terminal?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/loop&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Local terminal&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Desktop scheduled task&lt;/td&gt;
&lt;td&gt;Your machine&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Routine&lt;/td&gt;
&lt;td&gt;Anthropic cloud&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The three trigger types
&lt;/h2&gt;

&lt;p&gt;Every Routine needs at least one trigger. You can also stack them on the same Routine.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Schedule triggers&lt;/strong&gt; run on a recurring cadence. Hourly is the minimum interval. Daily, weekdays, and weekly are the built-in presets. For something like "every two hours" or "first Monday of the month," you pick the closest preset in the form and then run &lt;code&gt;/schedule update&lt;/code&gt; in the CLI to set a specific cron expression after creation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;API triggers&lt;/strong&gt; give the Routine a dedicated HTTP endpoint. POST to it with a bearer token and optionally pass a &lt;code&gt;text&lt;/code&gt; field for run-specific context. This is what makes Routines composable with the rest of your stack: your deploy pipeline, your alerting system, a webhook from Sentry. Any service that can make an authenticated HTTP request can trigger a Routine.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;GitHub triggers&lt;/strong&gt; react to repository events automatically. A PR is opened. A release is published. A labeled PR gets a new commit pushed. You add filters to narrow which events fire: base branch equals &lt;code&gt;main&lt;/code&gt;, is draft is &lt;code&gt;false&lt;/code&gt;, author contains a specific username.&lt;/p&gt;

&lt;p&gt;One Routine can use multiple triggers. A code review Routine might run nightly (for anything opened the day before), fire on every new PR, and be callable via API from your CI pipeline. All three wired to the same prompt.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;a href="https://hafiz.dev/blog/claude-code-routines-laravel-autopilot" rel="noopener noreferrer"&gt;View the interactive diagram on hafiz.dev&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Five Laravel use cases worth setting up
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Nightly PR review
&lt;/h3&gt;

&lt;p&gt;The most immediately useful Routine for any active Laravel project. Claude reviews all open PRs opened in the last 24 hours, runs your &lt;a href="https://hafiz.dev/blog/laravel-pest-4-testing-complete-guide" rel="noopener noreferrer"&gt;Pest test suite&lt;/a&gt;, flags anything touching auth or database migrations, and leaves inline comments.&lt;/p&gt;

&lt;p&gt;Prompt to use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Review all open pull requests in this repository that were opened in the last 24 hours and have no reviewer assigned.

For each PR:
- Run the Pest test suite and report any failures
- Check that migrations are reversible and include both up() and down() methods
- Flag any raw queries that bypass Eloquent
- Check that jobs implement ShouldQueue and define both $tries and $timeout
- Flag any use of env() outside of config files
- Leave inline review comments for issues found
- Add a summary comment listing all issues, or confirming the PR is clean

Do not approve the PR. Add a "needs-changes" label if any issues are found, and "passed-ai-review" if clean.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Set a schedule trigger for every weekday at 8:00 AM. You start the morning with feedback already posted. Human reviewers can focus on architecture instead of catching &lt;code&gt;env()&lt;/code&gt; in the wrong place.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Queue health check
&lt;/h3&gt;

&lt;p&gt;Horizon is great but it doesn't catch everything. A nightly Routine can run queue diagnostics on your Laravel project and ping you only when something is actually wrong, not just generate noise.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Using the environment variables available in this session, run the following checks:

1. Run php artisan queue:monitor against all configured queue drivers
2. Check the failed_jobs table for any entries created in the last 24 hours
3. Hit the /horizon/api/stats endpoint and verify Horizon is running and workers are active
4. Check the application log for any CRITICAL or ERROR entries from the last 24 hours

If all checks pass, exit without posting anything.
If any check fails, post a message to the #alerts Slack channel with the failure details and the exact artisan commands to fix it.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll need a Slack MCP connector configured and your environment credentials set as environment variables in the cloud environment. For a full list of queue-related &lt;a href="https://hafiz.dev/laravel/artisan-commands" rel="noopener noreferrer"&gt;Artisan Commands&lt;/a&gt;, the reference page is worth bookmarking when you're building out the prompt.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Automated code review on PR open
&lt;/h3&gt;

&lt;p&gt;Set a GitHub trigger to fire on &lt;code&gt;pull_request.opened&lt;/code&gt; with one filter: is draft is &lt;code&gt;false&lt;/code&gt;. This catches every non-draft PR the moment it's opened and runs your team's checklist before a human reviewer even looks at it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;A new pull request has just been opened. Apply our Laravel code review checklist:

- New controllers must use the single-action pattern (one public __invoke method only)
- Form Requests used for all validation, never validate() inside controllers
- No env() calls outside of config files
- No missing $fillable on new Eloquent models
- All new jobs implement ShouldQueue and define $tries and $timeout
- Flag any database queries inside loops (N+1 problem) with the exact file and line
- No direct use of DB::statement() or DB::select() without a comment explaining why

Leave an inline comment for each violation. Post a summary comment at the bottom of the PR. Add label "passed-ai-review" if clean, or "needs-changes" if violations were found. Do not approve or request changes on the PR directly.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The filter matters here. &lt;code&gt;is draft: false&lt;/code&gt; means the Routine only fires on PRs that are actually ready for review. You don't want it running every time someone pushes a commit to a draft.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Post-deploy smoke test via API
&lt;/h3&gt;

&lt;p&gt;Wire your CD pipeline to trigger a Routine every time a production deploy completes. The Routine runs smoke checks against the new build and posts the result to your release channel.&lt;/p&gt;

&lt;p&gt;The API trigger gives you a &lt;code&gt;text&lt;/code&gt; field you can use to pass deploy context. From your deploy script:&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;-X&lt;/span&gt; POST https://api.anthropic.com/v1/claude_code/routines/trig_01XXXXX/fire &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer YOUR_ROUTINE_TOKEN"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"anthropic-beta: experimental-cc-routine-2026-04-01"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"anthropic-version: 2023-06-01"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&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;'{"text": "Deploy complete. SHA: abc123. Environment: production."}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note: the bearer token here is your Routine-specific token, not your Anthropic API key. Generate it from the API trigger setup in the web UI. It's shown once, so store it in your secrets manager immediately.&lt;/p&gt;

&lt;p&gt;The Routine prompt can reference the deploy context passed via &lt;code&gt;text&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;A production deploy has just completed. The deploy details are in your session context.

Run the following smoke checks:
1. Hit the /health endpoint and verify a 200 response
2. Run php artisan about to confirm the application is responding
3. Run php artisan queue:monitor and report queue status
4. Check the error log for any new CRITICAL entries in the last 5 minutes

Post results to the #deployments Slack channel. Include the deploy SHA from the context. Mark as PASS if all checks succeed, FAIL with details if any check fails.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5. Weekly documentation drift detection
&lt;/h3&gt;

&lt;p&gt;Docs get stale. A weekly Routine that scans merged PRs, compares changed methods against your &lt;code&gt;/docs&lt;/code&gt; directory, and opens update PRs when it finds drift handles this automatically.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Scan all pull requests merged in the last 7 days.

For each merged PR:
1. Identify which PHP files changed
2. Check whether the changed public methods or classes are referenced in the /docs directory
3. If documentation references a changed method but looks outdated based on the diff, open a PR against the docs branch with a suggested update

Skip PRs that only changed tests, migrations, config files, or frontend assets. Only flag documentation that references changed public-facing methods or API endpoints.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Schedule it for Sunday at midnight. By Monday morning you have doc update PRs queued, not a manual task sitting in someone's backlog.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up your first Routine
&lt;/h2&gt;

&lt;p&gt;The fastest path is through the web UI. Go to &lt;a href="https://claude.ai/code/routines" rel="noopener noreferrer"&gt;claude.ai/code/routines&lt;/a&gt; and click &lt;strong&gt;New routine&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Give it a descriptive name. "Nightly PR Review" is better than "Routine 1." Write the prompt. This is the most important part: the Routine runs without you in the loop, so the prompt must be explicit about what to do and what success looks like. Vague prompts produce vague results, and there's no one watching to redirect them.&lt;/p&gt;

&lt;p&gt;Select your GitHub repository. Claude clones it fresh at the start of every run, starting from the default branch.&lt;/p&gt;

&lt;p&gt;Pick an environment. The Default environment works for most cases. If your Routine needs to call external APIs or read secrets like Sentry tokens or Slack webhooks, create a custom environment first and set those values as environment variables there.&lt;/p&gt;

&lt;p&gt;Add your trigger. For a scheduled Routine, pick the frequency. Times are set in your local timezone and converted automatically, so "9:00 AM" fires at 9:00 AM wherever you are, not in some UTC offset you have to calculate.&lt;/p&gt;

&lt;p&gt;Review the connectors. All connected MCP connectors are included by default. Remove anything the Routine doesn't actually need. A Routine that only reviews code and opens PRs has no reason to have Linear or Google Drive in scope.&lt;/p&gt;

&lt;p&gt;Click &lt;strong&gt;Create&lt;/strong&gt;. Hit &lt;strong&gt;Run now&lt;/strong&gt; on the detail page to test it immediately without waiting for the next scheduled time.&lt;/p&gt;

&lt;p&gt;From the CLI, run &lt;code&gt;/schedule&lt;/code&gt; in any Claude Code session to create a Routine conversationally. Useful when you know exactly what you want and don't want to click through the web UI. &lt;code&gt;/schedule list&lt;/code&gt; shows all your Routines, &lt;code&gt;/schedule update&lt;/code&gt; changes one, and &lt;code&gt;/schedule run&lt;/code&gt; triggers it immediately.&lt;/p&gt;

&lt;h2&gt;
  
  
  Branch permissions: the default that catches people
&lt;/h2&gt;

&lt;p&gt;By default, Routines can only push to branches prefixed with &lt;code&gt;claude/&lt;/code&gt;. This is intentional. An autonomous agent creating commits on &lt;code&gt;main&lt;/code&gt; or &lt;code&gt;develop&lt;/code&gt; is risky if the prompt isn't perfectly scoped.&lt;/p&gt;

&lt;p&gt;When you add a repository to a Routine, there's an &lt;strong&gt;Allow unrestricted branch pushes&lt;/strong&gt; toggle. Leave it off unless you have a specific need. The &lt;code&gt;claude/&lt;/code&gt; prefix keeps automated work clearly identifiable and makes cleanup straightforward if something goes sideways.&lt;/p&gt;

&lt;p&gt;If your Routine is creating PRs that look right but fails on push, this is almost certainly the reason. Enable unrestricted pushes for that repo in the Routine's settings.&lt;/p&gt;

&lt;p&gt;The flip side: PRs created by a Routine appear under your GitHub identity. Commits carry your username. Slack messages sent by a Routine's connector use your Slack account. The Routine acts as you, not as a bot account.&lt;/p&gt;

&lt;h2&gt;
  
  
  Plans and limits
&lt;/h2&gt;

&lt;p&gt;Routines are available on Pro, Max, Team, and Enterprise plans. They draw from your regular Claude Code subscription usage the same way interactive sessions do. There's also a daily cap on how many Routine runs can start per account, visible at &lt;a href="https://claude.ai/settings/usage" rel="noopener noreferrer"&gt;claude.ai/settings/usage&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you hit the daily cap, additional runs are rejected until the window resets. On Team and Enterprise plans with extra usage enabled, Routines continue on metered overage instead.&lt;/p&gt;

&lt;p&gt;GitHub event triggers also have per-routine and per-account hourly caps during the research preview. Events beyond the limit are dropped until the window resets. Worth keeping in mind if you're setting up a trigger on a high-volume repository.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to use GitHub Actions instead
&lt;/h2&gt;

&lt;p&gt;Routines are not a replacement for CI/CD. Running tests on every push, deploying to staging on merge, enforcing security checks before a PR can be merged, anything that needs to block a developer workflow belongs in GitHub Actions. It integrates with branch protections, runs in your own infrastructure, and has native visibility in the PR UI.&lt;/p&gt;

&lt;p&gt;Routines are better for background work that doesn't need to gate anything. Review, triage, documentation updates, monitoring. Think of it as the difference between a gatekeeper (CI/CD) and an assistant handling repetitive work in the background.&lt;/p&gt;

&lt;p&gt;Also, Routines run with no approval prompts. That's the point, but it's also a risk. Test any Routine with &lt;strong&gt;Run now&lt;/strong&gt; and review what it actually did before leaving it to run unattended. A badly scoped PR review prompt that opens 30 spurious PRs is not a great Monday morning.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://hafiz.dev/blog/the-complete-laravel-claude-code-ecosystem-every-tool-plugin-and-config-you-actually-need" rel="noopener noreferrer"&gt;Claude Code ecosystem&lt;/a&gt; post covers where Routines fit alongside other pieces like CLAUDE.md, skills, MCP, and the plugin marketplace if you want the full picture.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Do Routines work on private repositories?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Yes. Routines clone your connected GitHub repositories, including private ones, as long as you've granted the necessary access via the web setup flow or &lt;code&gt;/web-setup&lt;/code&gt; in the CLI. GitHub event triggers specifically require installing the Claude GitHub App on the repository. Repository access via &lt;code&gt;/web-setup&lt;/code&gt; is separate and doesn't install the App automatically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's the difference between a Routine and a GitHub Action that calls Claude Code?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;GitHub Actions run on GitHub's infrastructure, count against your GitHub Actions minutes, and can gate PRs and deployments through required status checks. Routines run on Anthropic-managed infrastructure and count against your Claude Code subscription. Use GitHub Actions when you need to block something. Use Routines for background work that doesn't need to block anything.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I share Routines with teammates?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Not currently. Routines belong to your individual claude.ai account and aren't shared. Everything a Routine does through your GitHub identity or connectors appears as you. For team workflows where multiple people need the same automation, each person sets up their own Routine, or you handle it through GitHub Actions with shared secrets.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I pass different context on each API trigger call?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Yes. The optional &lt;code&gt;text&lt;/code&gt; field in the API request body is passed to the Routine as run-specific context alongside its saved prompt. Pass anything: a Sentry alert body, a deploy SHA, a list of changed files. The Routine receives it as a literal string, so don't send structured JSON expecting it to be parsed. If you need structured data, serialize it yourself and parse it in the prompt instructions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What happens when a Routine run fails mid-session?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The run session stays visible at &lt;code&gt;claude.ai/code/routines&lt;/code&gt;. You can open it, see exactly what Claude did, and continue the conversation manually if needed. The Routine itself keeps running on future triggers. Only that individual run failed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Routines flip the default. Instead of Claude Code being a tool you actively drive, it becomes something that works in the background while you're focused on other things, or asleep. The nightly PR review alone is worth the setup time on any project with more than one contributor.&lt;/p&gt;

&lt;p&gt;Start with one. Set up the nightly PR review, run it manually a few times to tune the prompt, then leave it running. Once you trust it, you'll think of five more things to automate.&lt;/p&gt;

&lt;p&gt;Building a Laravel application that needs architectural review or a second set of eyes on code quality? &lt;a href="mailto:contact@hafiz.dev"&gt;Get in touch&lt;/a&gt; and let's talk about what that looks like.&lt;/p&gt;

</description>
      <category>laravel</category>
      <category>claudecode</category>
      <category>aidevelopment</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
