<?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: Dawid Makowski</title>
    <description>The latest articles on DEV Community by Dawid Makowski (@makowskid).</description>
    <link>https://dev.to/makowskid</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1136220%2Fb1d5a79a-620a-4979-a704-60a3cddf744b.jpg</url>
      <title>DEV Community: Dawid Makowski</title>
      <link>https://dev.to/makowskid</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/makowskid"/>
    <language>en</language>
    <item>
      <title>Your AI API, your rules: Introducing Custom AI Job instructions</title>
      <dc:creator>Dawid Makowski</dc:creator>
      <pubDate>Thu, 02 Apr 2026 13:26:10 +0000</pubDate>
      <link>https://dev.to/makowskid/your-ai-api-your-rules-introducing-custom-ai-job-instructions-nbf</link>
      <guid>https://dev.to/makowskid/your-ai-api-your-rules-introducing-custom-ai-job-instructions-nbf</guid>
      <description>&lt;h2&gt;
  
  
  The problem with one-size-fits-all AI
&lt;/h2&gt;

&lt;p&gt;SharpAPI's endpoints are built to work out of the box. You send a resume, you get a score. You send a job title, you get a description. That's the promise, and it holds for most use cases.&lt;/p&gt;

&lt;p&gt;The workaround until now was wrapping our API in your own middleware, manually injecting context before every call. That works, but it's boilerplate you shouldn't have to write.&lt;/p&gt;

&lt;p&gt;We shipped a native per-account, per-endpoint instruction system. You write the context once. We inject it into every relevant AI request automatically, at the right layer, without touching your API calls.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;p&gt;Every SharpAPI account now has a &lt;strong&gt;Customize AI Jobs&lt;/strong&gt; section in the dashboard. It lists all available AI job types grouped by category, from HR and recruitment endpoints to e-commerce and content tools.&lt;/p&gt;

&lt;p&gt;For each job type, you can write a free-form instruction. Think of it as a persistent system note to the underlying model: "for every resume scoring request I make, apply these priorities." Once saved and toggled active, that instruction is automatically injected before the AI processes your request.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Open the dashboard and go to Customize AI Jobs&lt;/strong&gt; Find it in the sidebar, right below Custom Workflows. All job types are listed and grouped by category.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2: Expand the endpoint you want to customize&lt;/strong&gt; Each card has an inline editor. Write your instruction in plain language, no special syntax required.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3: Save and activate&lt;/strong&gt; You'll see a confirmation prompt before saving, since changing instructions affects your output. Toggle it on, and you're done.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 4: Your API calls stay exactly the same&lt;/strong&gt; No changes to request format, headers, or parameters. The customization happens server-side, invisibly, for every matching request.&lt;/p&gt;




&lt;h2&gt;
  
  
  What this looks like in practice
&lt;/h2&gt;

&lt;p&gt;Say you're using the Resume Scoring endpoint to screen candidates for a Cloud Architect role. Without a custom prompt, the model scores resumes against a generic template. With one, you can shift those priorities permanently for your account:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Weight cloud certifications (AWS, GCP, Azure) significantly higher than general programming experience. Penalize resumes with no evidence of distributed systems work. Flag any candidate with fewer than 3 years of hands-on infrastructure experience regardless of their total years in software.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Your API call stays unchanged. But behind the scenes, your custom instruction is already baked in. Every candidate's resume is scored against your actual hiring criteria, not a generic baseline.&lt;/p&gt;




&lt;h2&gt;
  
  
  What you can customize
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Tone and style&lt;/strong&gt; Lock content generation to your brand voice. Formal, conversational, regional, industry-specific -- it's up to you.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scoring weights&lt;/strong&gt; Boost or suppress specific signals in scoring endpoints. Relevant for resume scoring, content quality checks, and more.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Output structure&lt;/strong&gt; Tell the model to always include or exclude certain fields, use specific phrasing, or follow your internal format.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Domain context&lt;/strong&gt; Give the model background it otherwise wouldn't have: your industry, your audience, your product category.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note on Custom Workflows:&lt;/strong&gt; Custom Workflows already support user-defined prompts as a first-class feature. Custom Job Prompts do not apply to Workflow requests -- they're scoped to predefined endpoints only.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Managing your prompts
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Action&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Save a prompt&lt;/td&gt;
&lt;td&gt;Stores the instruction and activates it immediately&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Toggle off&lt;/td&gt;
&lt;td&gt;Disables the prompt without deleting it. Default AI behavior restored.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Toggle on&lt;/td&gt;
&lt;td&gt;Re-activates a previously saved prompt&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Delete&lt;/td&gt;
&lt;td&gt;Removes the prompt entirely. Endpoint returns to default behavior.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Every save triggers a confirmation dialog. This is intentional: custom prompts can meaningfully change your outputs, and we want the decision to feel deliberate rather than accidental.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Heads up:&lt;/strong&gt; Custom prompts are powerful, but they can also produce unexpected results if they conflict with the underlying endpoint logic. If your results start looking off, toggling the prompt off is the fastest way to confirm whether it's the source of the issue.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  What this means for teams and integrators
&lt;/h2&gt;

&lt;p&gt;If you're building a product on top of SharpAPI and serving multiple clients, custom prompts let you pre-configure the AI layer for each account without maintaining separate middleware or prompt injection logic on your end. Each account operates independently: one customer's scoring weights don't bleed into another's.&lt;/p&gt;

&lt;p&gt;For solo developers, this is simply a way to stop re-writing the same context in every API wrapper you build. Write it once, use it forever, change it whenever your needs shift.&lt;/p&gt;




&lt;h2&gt;
  
  
  Get started
&lt;/h2&gt;

&lt;p&gt;Custom Job Prompts are available to all accounts with API access. No plan upgrade required. Log into your dashboard, find &lt;strong&gt;Customize AI Jobs&lt;/strong&gt; in the sidebar, and start tuning.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;a href="https://sharpapi.com/en/dashboard/custom-prompts" rel="noopener noreferrer"&gt;Open Dashboard&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>api</category>
      <category>custom</category>
    </item>
    <item>
      <title>The Cloud Is Just Someone Else's Computer. Sometimes That Computer Gets Hit by a Drone.</title>
      <dc:creator>Dawid Makowski</dc:creator>
      <pubDate>Tue, 31 Mar 2026 08:58:36 +0000</pubDate>
      <link>https://dev.to/makowskid/the-cloud-is-just-someone-elses-computer-sometimes-that-computer-gets-hit-by-a-drone-2od1</link>
      <guid>https://dev.to/makowskid/the-cloud-is-just-someone-elses-computer-sometimes-that-computer-gets-hit-by-a-drone-2od1</guid>
      <description>&lt;h3&gt;
  
  
  When "Multi-Region Strategy" Means "Outrunning a Military Conflict"
&lt;/h3&gt;

&lt;p&gt;So last week a military drone blew up the AWS data center where my customer's platform runs. The platform serves millions of users across seven countries. I had to spend about a week moving everything from Bahrain to Europe. By hand. Because every single automated migration tool was also broken. Because, you know, the drones.&lt;/p&gt;

&lt;p&gt;I run a software consultancy. I've been in tech long enough to have planned for almost every disaster imaginable. Floods, earthquakes, ransomware, that one guy who drops the production database on a Friday afternoon. "Military drone strike on your cloud provider" was never on the list.&lt;/p&gt;

&lt;p&gt;And Bahrain is not an isolated case. Right now, data centers in more than ten countries are being targeted or threatened by either Iranian or russian drones. This isn't a regional incident. It's a global pattern.&lt;/p&gt;

&lt;p&gt;And yet, here we are. Welcome to DevOps in 2026.&lt;/p&gt;

&lt;h2&gt;
  
  
  Disaster Recovery Used to Mean Hurricanes. Now It Means Drones.
&lt;/h2&gt;

&lt;p&gt;If you've worked in infrastructure long enough, you've imagined the disaster scenarios. An earthquake takes out a data center in Tokyo. Hurricane floods a facility in Virginia. Maybe a biblical-scale power outage somewhere in Texas (actually, that one happens pretty regularly). You build for resilience, you plan your failovers, you sleep slightly less terribly at night.&lt;/p&gt;

&lt;p&gt;And I don't say "earthquake" lightly. Exactly a year ago, my wife and I were on the top floor of our skyscraper condo in Bangkok when a 7.7 magnitude earthquake hit. One second I was pushing a commit. The next second I was crawling on the floor. The building was swaying two meters each side, and water from the rooftop pool came crashing through into our living room. I still get flashbacks from that. So yes, I understand natural disasters on a very personal, visceral level. I expected those to be the thing that would eventually force me to move servers under pressure.&lt;/p&gt;

&lt;p&gt;What I never rehearsed was: "Your entire AWS region is down because a military drone hit all availability zones in Bahrain."&lt;/p&gt;

&lt;p&gt;Yet here we are.&lt;/p&gt;

&lt;p&gt;In early March, Iranian drones struck multiple AWS facilities across the UAE and Bahrain. This wasn't some theoretical threat model from a security conference whiteboard. This was the first confirmed military attack on a major hyperscale cloud provider's infrastructure. Banking apps went down. Payment systems collapsed. Delivery platforms across the Gulf went dark. And somewhere in Thailand, my phone started buzzing with messages from a very worried customer in Saudi Arabia.&lt;/p&gt;

&lt;h2&gt;
  
  
  There's No Terraform Module for Surviving a War Zone
&lt;/h2&gt;

&lt;p&gt;Here's what you need to understand about the week that followed: every single automated migration tool AWS provides was broken. CloudWatch, the thing that tells you if your servers are even alive? Gone. RDS Snapshots, the thing you use to back up databases before you touch anything? Unavailable. Cross-region transfer? Dead. AMI copies? Nope.&lt;/p&gt;

&lt;p&gt;It was like showing up to a house fire and discovering that not only is your fire truck empty, but someone also stole the hydrant.&lt;/p&gt;

&lt;p&gt;So I did what any reasonable engineer would do. I had to rebuilt multiple production environments from scratch, on bare Linux images, in Europe. By hand. For a platform serving millions of users across seven countries. I wrote custom scripts to export, compress, and transfer everything over the public internet (because AWS's own internal backbone between regions was also down). I wrote manual rescue scripts for files that kept failing for days with &lt;code&gt;InternalError&lt;/code&gt;. I worked nights because often it was the only window where platform traffic was low enough to safely verify everything.&lt;/p&gt;

&lt;p&gt;One week of controlled chaos. And by the end of it, the entire platform was running smoothly from Europe, as if nothing had happened.&lt;/p&gt;

&lt;p&gt;But everything had happened.&lt;/p&gt;

&lt;h2&gt;
  
  
  We're a Software Company. Why Do We Keep Running From Wars?
&lt;/h2&gt;

&lt;p&gt;I could tell this story as a purely technical narrative. Here's the architecture, here's the migration plan, here's the clever script that saved the day. But that would miss the point entirely.&lt;/p&gt;

&lt;p&gt;Because here's what my day-to-day actually looks like:&lt;/p&gt;

&lt;p&gt;I run a small tech consultancy. We build custom software. We manage cloud infrastructure. We automate businesses with AI workflows. Very normal stuff. And yet somehow, every single person on my team has been touched by war. Not metaphorically. Literally.&lt;/p&gt;

&lt;p&gt;I live in Thailand, which recently had skirmishes with Cambodia along the border. My Iranian engineer had to flee Iran with his entire family. One of my coworkers lives in Ukraine, literally in a war zone, delivering code between power cuts because the grid keeps getting hit by Iranian-designed drones. A couple of months ago he went to an immigration office across the border and couldn't come back for days because russians bombed the only bridge on his route home. Another colleague had to evacuate Ukraine with his whole family.&lt;/p&gt;

&lt;p&gt;We write code and configure servers. We're not defense contractors. We're not geopolitical analysts. We're developers who just want to ship clean code and go home.&lt;/p&gt;

&lt;p&gt;And yet, every week, somewhere on this planet, a conflict reaches through the internet cables and grabs us by the collar.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Strangest Plot Twist of 2026
&lt;/h2&gt;

&lt;p&gt;And now, in what might be the most unexpected geopolitical crossover episode of the decade: Ukraine is protecting Saudi skies.&lt;/p&gt;

&lt;p&gt;Let that sink in for a second. The country that has been fighting for its own survival since 2022, that has become the world's foremost expert on shooting down drones because it had no choice, has just signed defense cooperation agreements with Saudi Arabia, Qatar, and the UAE. Over 200 Ukrainian drone-countering specialists are now deployed across the Gulf, helping defend the very region where my customer's servers used to live.&lt;/p&gt;

&lt;p&gt;The same drones that forced me to migrate infrastructure out of Bahrain? Ukraine knows those drones intimately. They've been dealing with their Iranian-made cousins, the Shaheds, for years.&lt;/p&gt;

&lt;p&gt;So now the country of my colleague who codes between blackouts is also the country protecting the airspace above my customer's business. If you wrote this as fiction, your editor would tell you it's too on the nose.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who Else Is Living This?
&lt;/h2&gt;

&lt;p&gt;I can't be the only one. There must be thousands of engineers, sysadmins, CTOs, and DevOps folks out there who have spent the last few years making decisions that no technical manual covers. Moving workloads because of missiles. Rerouting traffic because of sanctions. Keeping systems alive through infrastructure that's being actively targeted.&lt;/p&gt;

&lt;p&gt;If you've had to migrate production systems because of armed conflict, I'd love to hear your story.&lt;/p&gt;

&lt;h2&gt;
  
  
  The New Normal (Which Is Not Normal At All)
&lt;/h2&gt;

&lt;p&gt;Twenty years ago, your biggest infrastructure worry was a hard drive failing or router dropping packets. Ten years ago, it was maybe a ransomware attack. Today, it's a state-sponsored drone strike on your cloud provider's physical data center.&lt;/p&gt;

&lt;p&gt;We've entered an era where "disaster recovery" needs to account for actual disasters of the military kind. Where your multi-region strategy isn't just about latency and compliance, it's about geopolitical risk assessment.&lt;/p&gt;

&lt;p&gt;The conflicts we see on the news aren't happening "over there" anymore. They're happening inside our dashboards, our uptime monitors, our incident channels. Every single one of us in tech is connected to these events whether we like it or not.&lt;/p&gt;

&lt;p&gt;The world got very small, and very complicated, very fast.&lt;/p&gt;




&lt;p&gt;Check more at &lt;a href="https://dawidmakowski.com/en/2026/03/the-cloud-is-just-someone-elses-computer-sometimes-that-computer-gets-hit-by-a-drone/" rel="noopener noreferrer"&gt;my blog&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>dataengineering</category>
      <category>war</category>
    </item>
    <item>
      <title>Introducing Custom Workflows: Turn Any AI Prompt Into a Production API</title>
      <dc:creator>Dawid Makowski</dc:creator>
      <pubDate>Mon, 16 Mar 2026 16:28:07 +0000</pubDate>
      <link>https://dev.to/makowskid/introducing-custom-workflows-turn-any-ai-prompt-into-a-production-api-3h05</link>
      <guid>https://dev.to/makowskid/introducing-custom-workflows-turn-any-ai-prompt-into-a-production-api-3h05</guid>
      <description>&lt;p&gt;&lt;em&gt;Photo by &lt;a href="https://unsplash.com/@campaign_creators?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Campaign Creators&lt;/a&gt; on &lt;a href="https://unsplash.com/photos/man-writing-on-white-board---kQ4tBklJI?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Building AI-powered features into your product usually means weeks of backend development, prompt engineering, infrastructure setup, and deployment headaches. What if you could skip all of that and go from an idea to a live REST API endpoint in under 60 seconds?&lt;/p&gt;

&lt;p&gt;That is exactly what &lt;a href="https://sharpapi.com/custom-workflows" rel="noopener noreferrer"&gt;&lt;strong&gt;Custom Workflows&lt;/strong&gt;&lt;/a&gt; delivers. A visual builder in the SharpAPI Dashboard lets you define typed input parameters, write your processing logic as a plain-English prompt, and generate a production-ready API endpoint on the spot. The result is a fully authenticated REST API that accepts your inputs, runs them through the AI model of your choice, and returns structured JSON matching the output schema you defined. No backend code. No deployment pipeline. No infrastructure to manage. Just your idea, instantly available as an endpoint.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem: AI Integration Is Still Too Hard
&lt;/h2&gt;

&lt;p&gt;Every development team has a backlog of AI features they want to build. Extract data from invoices. Classify support tickets. Generate product descriptions. Score leads. Moderate content.&lt;/p&gt;

&lt;p&gt;Each of these features follows the same pattern: take some input, send it to an AI model, parse the response, and return structured data. And yet, building each one from scratch means writing boilerplate code, handling API communication with model providers, parsing unpredictable outputs into reliable schemas, setting up async processing for long-running tasks, and deploying the whole thing somewhere.&lt;/p&gt;

&lt;p&gt;Custom Workflows eliminates every one of those steps.&lt;/p&gt;

&lt;h2&gt;
  
  
  How It Works: A 5-Step Visual Builder
&lt;/h2&gt;

&lt;p&gt;The Custom Workflows builder lives right in the SharpAPI Dashboard. It walks you through five straightforward steps:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Basic Info.&lt;/strong&gt; Give your workflow a name and description, then choose your input mode (JSON for structured data or Form-Data for file uploads).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2: Parameters.&lt;/strong&gt; Define your input parameters with types, labels, default values, and required/optional flags. The system supports the full range of types you would expect: strings, numbers, booleans, arrays, and objects for JSON mode, plus text and file inputs for Form-Data mode.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3: AI Prompt.&lt;/strong&gt; Write what you want the AI to do, in plain English. No prompt engineering expertise needed. If you are not sure where to start, pick one of the pre-made templates (more on those below) and customize it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 4: Output Schema.&lt;/strong&gt; Define the JSON structure you want back. Or just click one button and let the AI analyze your workflow context to suggest an optimal schema automatically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 5: Review &amp;amp; Save.&lt;/strong&gt; Preview everything, activate, and grab your endpoint URL. You are live.&lt;/p&gt;

&lt;p&gt;That is the whole process. Five steps, and you have a production API.&lt;/p&gt;

&lt;h2&gt;
  
  
  What You Get: A Real API, Not a Toy
&lt;/h2&gt;

&lt;p&gt;Every saved workflow immediately becomes a live REST endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;POST /api/v1/custom/{your-workflow-slug}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It uses the same API key authentication (&lt;code&gt;X-API-KEY&lt;/code&gt; header) as every other SharpAPI endpoint. No additional setup or configuration.&lt;/p&gt;

&lt;p&gt;Processing follows the standard SharpAPI async pattern for reliability at scale. You send a POST request and get back a &lt;code&gt;202 Accepted&lt;/code&gt; with a &lt;code&gt;status_url&lt;/code&gt; and &lt;code&gt;job_id&lt;/code&gt;. Poll the status URL until the job completes, then retrieve your structured JSON result matching the exact output schema you defined.&lt;/p&gt;

&lt;p&gt;Every workflow also exposes a self-describing GET endpoint that returns its complete specification: parameter definitions, types, labels, required flags, defaults, output schema, and endpoint URL. This is perfect for auto-generating client code or feeding into other AI systems that need to understand what your API does.&lt;/p&gt;

&lt;h2&gt;
  
  
  Type Safety Without the Boilerplate
&lt;/h2&gt;

&lt;p&gt;The parameter system is strict by design. Every input parameter is validated against its declared type before any processing begins. Required parameters must be present. Default values fill in for optional ones. And in JSON mode, unknown parameters are automatically rejected, preventing typos and unexpected inputs from sneaking through.&lt;/p&gt;

&lt;p&gt;This gives you the kind of type safety you would normally need backend code to enforce, except you get it for free.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-World Use Cases
&lt;/h2&gt;

&lt;p&gt;Here is where it gets practical. Custom Workflows can power a wide range of features across industries:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Document Processing.&lt;/strong&gt; Upload invoices, contracts, receipts, or forms and extract structured data at scale. Pair Form-Data input mode with a file parameter and you have a document processing API in minutes. Think insurance claim forms, medical records intake, shipping manifests, purchase orders, or tax documents.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Content Analysis.&lt;/strong&gt; Sentiment analysis, content moderation, tone detection, urgency classification. Feed text in, get structured scores and labels out. Great for analyzing customer reviews, social media mentions, support tickets, survey responses, app store feedback, and forum posts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Content Generation.&lt;/strong&gt; Automated email drafts, product descriptions, social media posts, marketing copy. Consistent, on-brand content delivered via API. Use it for personalized outreach emails, SEO meta descriptions, product listing copy for e-commerce catalogs, newsletter sections, ad variations, or localized marketing content.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Data Enrichment.&lt;/strong&gt; Add AI-powered insights, classifications, and predictions to your existing data pipelines. Plug a Custom Workflow into your ETL process and enrich records as they flow through. Classify CRM leads by intent, tag support conversations by topic, extract skills from resumes, or categorize transactions by spend type.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Risk Assessment.&lt;/strong&gt; Compliance checking, fraud detection signals, policy violation screening. Get structured risk scores through a simple API call. Screen user-generated content for policy violations, flag suspicious transaction patterns, evaluate vendor contracts for non-standard clauses, or check marketing copy against regulatory guidelines.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Customer Support Automation.&lt;/strong&gt; Route incoming tickets to the right team, suggest reply templates, extract key details (order numbers, product names, issue categories) from customer messages, or generate first-draft responses for agents to review.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;E-commerce &amp;amp; Product.&lt;/strong&gt; Automate product categorization and tagging, generate comparison tables, extract specs from manufacturer datasheets, translate listings for international storefronts, or normalize product attributes across multiple suppliers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;HR &amp;amp; Recruitment.&lt;/strong&gt; Parse resumes and extract structured candidate profiles, screen cover letters, generate interview question sets tailored to job descriptions, summarize employee feedback surveys, or classify internal knowledge base articles.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Legal &amp;amp; Finance.&lt;/strong&gt; Summarize contract clauses, extract key terms and obligations, flag non-standard language in agreements, categorize expense reports, or generate structured abstracts from regulatory filings.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Education &amp;amp; Research.&lt;/strong&gt; Summarize academic papers, extract citations and key findings, generate quiz questions from study materials, classify research topics, or transform lecture notes into structured outlines.&lt;/p&gt;

&lt;h2&gt;
  
  
  SDKs for Every Stack
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Official SDKs are available for &lt;strong&gt;PHP&lt;/strong&gt;, &lt;strong&gt;Laravel&lt;/strong&gt;, &lt;strong&gt;Python&lt;/strong&gt;, and &lt;strong&gt;JavaScript/TypeScript&lt;/strong&gt;. Browse all Custom Workflow SDK packages on &lt;a href="https://github.com/orgs/sharpapi/repositories?q=custom" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Pricing: No Surprises
&lt;/h2&gt;

&lt;p&gt;Custom Workflows use the same credit-based billing system as every other SharpAPI endpoint. There is no extra charge, no separate pricing tier, no hidden fees. If you have credits, you can run workflows.&lt;/p&gt;

&lt;p&gt;The feature is available on all plans, from the free tier up to enterprise. Scale and Enterprise plans include additional support for custom endpoint configuration, dedicated account management, and custom SLAs.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Numbers
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;30+ pre-built AI endpoints&lt;/strong&gt; already available alongside Custom Workflows&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unlimited custom workflows&lt;/strong&gt; on every plan&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;99.9% API uptime&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;SOC 2 Type II certified&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Get Started
&lt;/h2&gt;

&lt;p&gt;Custom Workflows is live in the SharpAPI Dashboard right now. Log in, click "Create Workflow," and have your first custom AI endpoint running in under a minute.&lt;/p&gt;

&lt;p&gt;Whether you are building a document processing pipeline, adding AI-powered content analysis to your SaaS product, or just prototyping a new feature idea, Custom Workflows gives you the fastest path from concept to production API.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ready to turn your AI prompts into production endpoints?&lt;/strong&gt; Head to &lt;a href="https://sharpapi.com/custom-workflows" rel="noopener noreferrer"&gt;https://sharpapi.com/custom-workflows&lt;/a&gt; and start building.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>api</category>
      <category>workflow</category>
      <category>custom</category>
    </item>
    <item>
      <title>You Just Gave OpenClaw the Keys to Your Entire Digital Life - on a VPS Server You Don't Know How to Secure</title>
      <dc:creator>Dawid Makowski</dc:creator>
      <pubDate>Tue, 24 Feb 2026 15:08:37 +0000</pubDate>
      <link>https://dev.to/makowskid/you-just-gave-openclaw-the-keys-to-your-entire-digital-life-on-a-vps-server-you-dont-know-how-to-36ab</link>
      <guid>https://dev.to/makowskid/you-just-gave-openclaw-the-keys-to-your-entire-digital-life-on-a-vps-server-you-dont-know-how-to-36ab</guid>
      <description>&lt;p&gt;You Put OpenClaw on a VPS. It Has Access to Everything. You Secured None of It. Let's Fix That in 30 Minutes.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This guide was written for the brave souls who saw the OpenClaw hype train, jumped aboard, spun up their first VPS, and then had the terrifying realization that they now need to learn Linux security. You're going to be fine. Probably.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;Look, I get it. You saw the hype. You saw the tweets. You saw some guy on Reddit say OpenClaw changed his life, and now you're sitting there at 2 AM with your very first VPS, a fresh install of &lt;a href="https://openclaw.ai/" rel="noopener noreferrer"&gt;OpenClaw&lt;/a&gt;, and the sudden realization that you just handed an AI assistant the keys to your email, your calendar, your Google Drive, your private documents, and basically your entire digital soul - all running on a server you've never secured before because, well, this is your first server.&lt;/p&gt;

&lt;p&gt;Now, let's be fair: a fresh Ubuntu installation isn't actually a house with no doors. Ubuntu ships with sensible defaults - no unnecessary open ports, no sketchy services running, SSH with reasonable settings. Credit where credit is due. &lt;strong&gt;But here's the problem:&lt;/strong&gt; you're not running a fresh Ubuntu installation anymore. You're running OpenClaw on top of it. With plugins. And skills. And extensions. And API keys to everything you own. &lt;/p&gt;

&lt;p&gt;Here's a fun fact to help you sleep tonight: every VPS that connects to the internet gets fully port-scanned by automated bots within &lt;strong&gt;10 to 20 minutes&lt;/strong&gt;. Not hours. Not days. &lt;em&gt;Minutes.&lt;/em&gt; &lt;/p&gt;

&lt;p&gt;But with OpenClaw and its ecosystem of plugins exposing additional services and APIs? Now you're &lt;em&gt;interesting.&lt;/em&gt; And on the internet, you do not want to be interesting.&lt;/p&gt;

&lt;p&gt;And look - I'm not here to trash OpenClaw. It's genuinely cool. The AI-assistant-on-your-own-server dream is alive and well. But let's be honest with ourselves for a moment: OpenClaw, plus all of its plugins, skills, and extensions, is not exactly what security researchers would call "airtight." It's more what they'd call "a fun afternoon." The platform is young, moving fast, and the attack surface grows with every skill you install. The real risk isn't Ubuntu's defaults - it's everything you're bolting on top of them.&lt;/p&gt;

&lt;p&gt;So since we can't fix OpenClaw's security overnight, let's make damn sure that the server underneath it is locked down tight, so that even if someone finds a vulnerability in a plugin, they hit a brick wall instead of a buffet.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The good news?&lt;/strong&gt; I recommend Ubuntu for your VPS, and I'm going to walk you through this whole thing step by step. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why Ubuntu?&lt;/strong&gt; Because it has a massive community, a mountain of security tools, and - this is the important part - any non-technical noob can harden it in about 30 minutes with proper guidance. &lt;/p&gt;

&lt;p&gt;Let's go. And please, for the love of everything, &lt;strong&gt;don't close your terminal until I tell you to.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 0 - Non-root user
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Before we do anything else&lt;/strong&gt; - if you're still logged in as &lt;code&gt;root&lt;/code&gt; like some kind of digital cowboy, we need to fix that immediately. Root is the god account. It can do anything, break anything, and delete anything, including itself. &lt;/p&gt;

&lt;p&gt;So let's create a proper user and give it sudo powers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;adduser ubuntu
usermod &lt;span class="nt"&gt;-aG&lt;/span&gt; &lt;span class="nb"&gt;sudo &lt;/span&gt;ubuntu
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, because typing your password every time you run &lt;code&gt;sudo&lt;/code&gt; gets old approximately 4 seconds after the first time, let's set up passwordless sudo. Run &lt;code&gt;visudo&lt;/code&gt; and add this line at the very bottom:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ubuntu ALL=(ALL) NOPASSWD:ALL
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From this point on, you do everything as &lt;code&gt;ubuntu&lt;/code&gt; and use &lt;code&gt;sudo&lt;/code&gt; when you need elevated privileges. Think of &lt;code&gt;root&lt;/code&gt; as the emergency fire axe behind glass - it's there if you need it, but you shouldn't be casually swinging it around on a Tuesday afternoon. &lt;/p&gt;

&lt;p&gt;Now copy your SSH key to the new user (&lt;code&gt;ssh-copy-id&lt;/code&gt; or manually paste it into &lt;code&gt;/home/ubuntu/.ssh/authorized_keys&lt;/code&gt;), log in as &lt;code&gt;ubuntu&lt;/code&gt; in a new terminal to make sure it works, and &lt;strong&gt;never log in as root again.&lt;/strong&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  Step 1: Set Your Timezone
&lt;/h2&gt;

&lt;p&gt;Before we do anything dramatic, let's make sure your server knows what time it is. This sounds trivial, but accurate timestamps in your logs are the difference between "I can see exactly when someone broke in" and "something happened... at some point...&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;dpkg-reconfigure tzdata
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pick your timezone from the menu. It's interactive.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2: Update Everything
&lt;/h2&gt;

&lt;p&gt;Your server shipped with software that was already outdated by the time you clicked "Deploy." Let's fix that.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;apt update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt upgrade &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This updates all your packages to their latest versions, patching known vulnerabilities. Think of it as putting on pants before leaving the house. Bare minimum.&lt;/p&gt;

&lt;p&gt;Now, because we both know you're going to forget to do this regularly (I know you, and I love you, but I know you), let's set up &lt;strong&gt;automatic security updates&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;unattended-upgrades &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;dpkg-reconfigure &lt;span class="nt"&gt;--priority&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;low unattended-upgrades
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This configures your server to automatically install critical security patches without you having to remember. It's like hiring a tiny robot butler whose only job is to lock the doors you keep leaving open.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3: Set Up Ubuntu Pro
&lt;/h2&gt;

&lt;p&gt;Ubuntu Pro gives you expanded security maintenance, kernel live patching, and compliance tools. And here's the kicker - &lt;strong&gt;it's free for up to 5 machines.&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;Go to &lt;a href="https://ubuntu.com/pro" rel="noopener noreferrer"&gt;ubuntu.com/pro&lt;/a&gt;, grab your token, and attach it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pro attach YOUR_TOKEN_HERE
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This extends your security coverage and covers thousands of additional packages. It's like getting the extended warranty, except it actually does something.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 4: Lock Down SSH
&lt;/h2&gt;

&lt;p&gt;SSH is how you talk to your server. It's also how &lt;em&gt;everyone else&lt;/em&gt; tries to talk to your server. By default, it's running on port 22, which is the first port every bot on the internet checks. That's like hiding your house key under the doormat - the one place literally everyone looks first.&lt;/p&gt;

&lt;p&gt;Edit your SSH config:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nano /etc/ssh/sshd_config
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's what your config should look like. I'll explain each line, because I respect you and your journey:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;Port 55222                            &lt;span class="c"&gt;# Move SSH off the default port. Not foolproof, but stops 90% of really lazy scanners.&lt;/span&gt;
LoginGraceTime 2m                     &lt;span class="c"&gt;# You get 2 minutes to authenticate. After that, goodbye.&lt;/span&gt;
PermitRootLogin no                    &lt;span class="c"&gt;# NOBODY logs in as root. Ever. Not even you. Especially you.&lt;/span&gt;
MaxAuthTries 5                        &lt;span class="c"&gt;# 5 wrong passwords and we hang up on you. Rude? Maybe. Secure? Yes.&lt;/span&gt;
PasswordAuthentication no             &lt;span class="c"&gt;# No passwords. Period. Keys only. Passwords are the cargo shorts of security.&lt;/span&gt;
PermitEmptyPasswords no               &lt;span class="c"&gt;# Just... no. Come on.&lt;/span&gt;
AllowUsers ubuntu                     &lt;span class="c"&gt;# Only the 'ubuntu' user can log in. Everyone else can go home.&lt;/span&gt;
X11Forwarding no                      &lt;span class="c"&gt;# No graphical forwarding. This is a server, not a gaming PC.&lt;/span&gt;
PermitUserEnvironment no              &lt;span class="c"&gt;# Don't let users set environment variables through SSH. Trust issues? You bet.&lt;/span&gt;
AllowAgentForwarding no               &lt;span class="c"&gt;# No SSH agent forwarding. Reduces the risk of key theft.&lt;/span&gt;
AllowTcpForwarding no                 &lt;span class="c"&gt;# No TCP tunneling through your server. It's not a VPN.&lt;/span&gt;
PermitTunnel no                       &lt;span class="c"&gt;# Same energy as above. No tunnels.&lt;/span&gt;
KbdInteractiveAuthentication &lt;span class="nb"&gt;yes&lt;/span&gt;      &lt;span class="c"&gt;# Needed for 2FA (we'll get there, be patient).&lt;/span&gt;
ChallengeResponseAuthentication &lt;span class="nb"&gt;yes&lt;/span&gt;   &lt;span class="c"&gt;# Also needed for 2FA. The dynamic duo.&lt;/span&gt;
AuthenticationMethods publickey,keyboard-interactive  &lt;span class="c"&gt;# Key first, then 2FA code. Belt AND suspenders.&lt;/span&gt;
UsePAM &lt;span class="nb"&gt;yes&lt;/span&gt;                            &lt;span class="c"&gt;# Use PAM for authentication. Required for Google Authenticator.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The key takeaways:&lt;/strong&gt; We moved the SSH port (so bots can't find it easily), disabled root login (so even if someone gets in, they're not god), killed password authentication (keys only, like a VIP club), and set up the groundwork for two-factor authentication.&lt;/p&gt;

&lt;p&gt;Now reload SSH so it actually pays attention to what we just told it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sshd &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; systemctl reload ssh.service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;sshd -t&lt;/code&gt; part tests your config first. If there's a typo, it'll tell you before you lock yourself out. Because locking yourself out of your own server is a very special kind of pain.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;⚠️ CRITICAL: Do NOT close your current terminal session yet. Open a NEW terminal and test that you can still connect with your new settings before closing anything.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh &lt;span class="nt"&gt;-i&lt;/span&gt; /path/to/your-key &lt;span class="nt"&gt;-p&lt;/span&gt; 55222 ubuntu@your-server-ip
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 5: Install Fail2Ban
&lt;/h2&gt;

&lt;p&gt;Fail2Ban watches your authentication logs and automatically bans IP addresses that fail to log in too many times. It's basically a nightclub bouncer for your server.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;fail2ban &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Out of the box, Fail2Ban will monitor SSH and ban anyone who fails authentication repeatedly. You can customize the jail settings later, but the defaults are already pretty solid for keeping the riff-raff out.&lt;/p&gt;

&lt;p&gt;Think of it this way: Step 4 made it harder to get in. Step 5 makes sure that anyone who keeps trying gets permanently shown the door.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 6: Set Up Two-Factor Authentication
&lt;/h2&gt;

&lt;p&gt;This is the big one. This is where we go from "pretty secure" to "okay, now I can actually sleep at night."&lt;/p&gt;

&lt;p&gt;Two-factor authentication means that even if someone somehow gets your SSH key (it happens - laptops get stolen, backups get leaked, your cat walks across your keyboard and emails it to someone), they STILL can't get in without the 6-digit code from your phone.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Install Google Authenticator:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;apt &lt;span class="nb"&gt;install &lt;/span&gt;libpam-google-authenticator &lt;span class="nt"&gt;-y&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Switch to your ubuntu user and run the setup:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;su - ubuntu
google-authenticator
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It'll ask you some questions. Here are the correct answers (you're welcome):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Prompt&lt;/th&gt;
&lt;th&gt;Answer&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Time-based tokens?&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;y&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Time-based is more secure than counter-based&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Update .google_authenticator file?&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;y&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;This saves your config&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Disallow multiple uses?&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;y&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Each code works exactly once&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Increase time window?&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;n&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Keep it tight&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rate limiting?&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;y&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Slows down brute-force attempts&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;It'll show you a QR code. Scan it with Google Authenticator, Authy, or whatever TOTP app you prefer. &lt;strong&gt;And for the love of all that is holy, SAVE THE EMERGENCY SCRATCH CODES.&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Put them in a password manager (important!). They're your "break glass in case of emergency" codes if you lose your phone.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Configure PAM&lt;/strong&gt; (this tells SSH to actually &lt;em&gt;use&lt;/em&gt; the authenticator):&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;sudo &lt;/span&gt;nano /etc/pam.d/sshd
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add this line &lt;strong&gt;at the very top:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;auth required pam_google_authenticator.so
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And &lt;strong&gt;comment out&lt;/strong&gt; this line (to prevent a double password prompt, which is annoying and unnecessary):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# @include common-auth
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Make sure your SSH config has these lines set correctly&lt;/strong&gt; (most of them should already be right from Step 4):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;KbdInteractiveAuthentication &lt;span class="nb"&gt;yes
&lt;/span&gt;ChallengeResponseAuthentication &lt;span class="nb"&gt;yes
&lt;/span&gt;AuthenticationMethods publickey,keyboard-interactive
UsePAM &lt;span class="nb"&gt;yes
&lt;/span&gt;PasswordAuthentication no
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Test the config and restart SSH:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sshd &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; systemctl restart ssh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Now test in a NEW terminal&lt;/strong&gt; (seriously, keep your current session open - are you sensing a pattern here?):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh &lt;span class="nt"&gt;-i&lt;/span&gt; /path/to/your-key &lt;span class="nt"&gt;-p&lt;/span&gt; 55222 ubuntu@your-server-ip
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your login flow should now be: &lt;strong&gt;SSH key → TOTP verification code → you're in.&lt;/strong&gt; No password involved. Just your key and your phone. It's like a secret handshake, but actually secure.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 7: Monitor Your Logs
&lt;/h2&gt;

&lt;p&gt;Congratulations, your server is now significantly more secure than it was 20 minutes ago. But you need to actually check on things occasionally.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Check your SSH authentication logs:&lt;/strong&gt;&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;sudo cat&lt;/span&gt; /var/log/auth.log | &lt;span class="nb"&gt;grep &lt;/span&gt;sshd
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;For live monitoring&lt;/strong&gt; (great for watching scans/attacks happen in real-time, which is weirdly entertaining):&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;sudo tail&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; /var/log/auth.log
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What to look for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Repeated failed login attempts&lt;/strong&gt; - Fail2Ban should catch these, but check anyway&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Login attempts from unfamiliar IP addresses&lt;/strong&gt; - If you see IPs you don't recognize, investigate&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unknown usernames&lt;/strong&gt; - If someone's trying to log in as "admin" or "test," that's a bot&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Successful logins at weird hours&lt;/strong&gt; - If you logged in at 3 AM and you were asleep at 3 AM, we have a problem&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Bonus Round: For the Ambitious
&lt;/h2&gt;

&lt;p&gt;If you've made it this far and you're feeling confident (possibly &lt;em&gt;too&lt;/em&gt; confident, but I respect the energy), here are two next-level options:&lt;/p&gt;

&lt;h3&gt;
  
  
  Tailscale
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://tailscale.com/" rel="noopener noreferrer"&gt;Tailscale&lt;/a&gt; creates a private mesh VPN between your devices. Once set up, you can access your server through a private network that isn't exposed to the public internet at all. It's like having a secret tunnel to your server that only you know about. The setup is shockingly simple for something this powerful.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cloudflare Tunnel
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/" rel="noopener noreferrer"&gt;Cloudflare Tunnel&lt;/a&gt; lets you expose your OpenClaw instance to the internet without opening ANY inbound ports on your server. Zero. None. The server reaches out to Cloudflare, and Cloudflare handles all incoming traffic. It's like having a P.O. Box for your server - people can send you mail, but they don't know where you live.&lt;/p&gt;

&lt;p&gt;Both of these are excellent options if you want to take your security from "solid" to "paranoid, but like, in a healthy way."&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;If you're running OpenClaw on a VPS, you've put your most private digital life on a server connected to the open internet. Your emails. Your calendar. Your documents. Your credentials. All of it sitting there, protected by whatever security you bothered to set up.&lt;/p&gt;

&lt;p&gt;Is your server now impenetrable? No. Nothing is impenetrable. But you've gone from being a soft, delicious target to being the server that's maybe not worth the effort today when there are millions of easier ones to hit. &lt;/p&gt;

&lt;p&gt;You've got this. Probably. I believe in you. Mostly.&lt;/p&gt;

</description>
      <category>openclaw</category>
      <category>ai</category>
      <category>agents</category>
    </item>
    <item>
      <title>How We Automated Invoice Processing for Our Clients</title>
      <dc:creator>Dawid Makowski</dc:creator>
      <pubDate>Mon, 23 Feb 2026 15:11:05 +0000</pubDate>
      <link>https://dev.to/makowskid/how-we-automated-invoice-processing-for-our-clients-1jae</link>
      <guid>https://dev.to/makowskid/how-we-automated-invoice-processing-for-our-clients-1jae</guid>
      <description>&lt;p&gt;If you've ever watched a finance team manually key in invoice data from a stack of PDFs, phone photos, and scanned documents, you know the look. It's somewhere between existential dread and quiet resignation. We've seen it across multiple client engagements - fintech, retail, logistics - and the story is always the same. Smart people doing dumb work because the tools they have can't handle the chaos of real invoices.&lt;/p&gt;

&lt;p&gt;So we fixed it.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Problem We Kept Running Into
&lt;/h3&gt;

&lt;p&gt;Across our client projects, invoice processing kept showing up as a bottleneck. Not because the concept is hard, but because the reality is messy. Invoices arrive as pristine PDFs sometimes, sure. But more often they show up as phone photos taken at weird angles with bad lighting, scanned TIFFs from office equipment that should have been retired a decade ago, or flattened PDFs where the text is baked into images and not selectable.&lt;/p&gt;

&lt;p&gt;Every off-the-shelf OCR solution we tried would work great on the clean files and fall apart on everything else. And "everything else" is most of what shows up in production.&lt;/p&gt;

&lt;h3&gt;
  
  
  What We Built
&lt;/h3&gt;

&lt;p&gt;We designed a multi-step AI pipeline that approaches invoice parsing the way a human would -- if that human could process thousands of documents per hour without making mistakes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1: ML-powered OCR&lt;/strong&gt; extracts the raw content from the file, regardless of format or quality. This isn't your basic Tesseract setup. It's trained to handle the messy stuff -- crumpled paper, shadows, skewed scans, multi-page documents.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2: AI processing rounds&lt;/strong&gt; take that raw extraction and run it through multiple validation and structuring passes. This is where the magic happens. The AI identifies what's a line item vs. a header vs. a tax calculation, maps seller and buyer information correctly, and resolves ambiguities that would trip up simpler systems.&lt;/p&gt;

&lt;p&gt;The result is a clean, structured JSON object with 100+ data fields covering everything you'd ever need from an invoice: document metadata, seller/buyer details with full addresses, financial breakdowns with tax calculations, individual line items, payment terms, logistics info, e-invoice metadata, and reference numbers.&lt;/p&gt;

&lt;h3&gt;
  
  
  What It Handles
&lt;/h3&gt;

&lt;p&gt;We built this for the real world, not demo day. That means it works with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;8 file formats&lt;/strong&gt;: PDF, DOC, DOCX, JPG, JPEG, PNG, TIFF, TIF&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Messy phone photos&lt;/strong&gt;: crumpled paper, bad lighting, weird angles -- the kind of stuff your field teams actually send in&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scanned invoices and flattened PDFs&lt;/strong&gt;: where the content is images, not selectable text&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-page invoices&lt;/strong&gt;: processes the full document, not just the first page&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-currency invoices&lt;/strong&gt;: extracts currency info, exchange rates, VAT/GST/SST IDs, and country-specific tax details&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;80+ languages&lt;/strong&gt;: from English to Japanese to Arabic&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  How Our Clients Use It
&lt;/h3&gt;

&lt;p&gt;Once we had this working, the use cases multiplied fast:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Accounts payable automation&lt;/strong&gt; - the obvious one. Invoices come in, structured data comes out, AP workflow picks it up. Processing time goes from minutes per invoice to seconds.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ERP and accounting integration&lt;/strong&gt; - clean, consistent data piped straight into QuickBooks, Xero, SAP, NetSuite, or whatever the client is running. No more "which field maps to what" conversations with the finance team.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Spend analytics&lt;/strong&gt; - when every invoice is structured data, building dashboards and running analyses across your entire vendor base becomes trivial. Clients use this to spot trends, negotiate better terms, and flag cost-saving opportunities.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fraud detection&lt;/strong&gt; - cross-referencing parsed invoice data against purchase orders and contracts to automatically flag discrepancies before payments go out.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Expense management and compliance&lt;/strong&gt; - automating expense report validation against company policies and maintaining audit trails without human intervention.&lt;/p&gt;

&lt;h3&gt;
  
  
  We Made It Available to Everyone
&lt;/h3&gt;

&lt;p&gt;Rather than keep this locked inside client projects, we productized the entire pipeline as part of &lt;a href="https://sharpapi.com/" rel="noopener noreferrer"&gt;SharpAPI&lt;/a&gt; -- our AI workflow automation API. The Invoice Parsing endpoint is live and available on all plans.&lt;/p&gt;

&lt;p&gt;Integration follows the same simple async pattern as all SharpAPI endpoints:&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;--location&lt;/span&gt; &lt;span class="s1"&gt;'https://sharpapi.com/api/v1/invoice/parse'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'Accept: application/json'&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;"Authorization: Bearer YOUR_API_TOKEN"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--form&lt;/span&gt; &lt;span class="s1"&gt;'file=@"invoice.pdf"'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;POST your file, get a job ID, poll for results. That's it.&lt;/p&gt;

&lt;p&gt;And because we know developers hate writing boilerplate HTTP code, there are ready-to-go SDK packages for &lt;strong&gt;PHP, Laravel, Node.js, Python, and .NET&lt;/strong&gt; on GitHub:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/orgs/sharpapi/repositories?q=invoice" rel="noopener noreferrer"&gt;Browse Invoice Parsing SDKs on GitHub&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Why This Matters for Your Business
&lt;/h3&gt;

&lt;p&gt;Manual invoice processing costs businesses an average of $15-$40 per invoice when you factor in labor, errors, and delays. At scale, that adds up fast. If your team processes a few hundred invoices a month, you're looking at significant savings just by automating the extraction step -- not to mention the reduction in errors that cause payment disputes, compliance issues, and vendor relationship headaches.&lt;/p&gt;

&lt;p&gt;We built this because we kept seeing the same problem across different industries and different clients. If your business deals with invoices at any meaningful volume, this is the kind of automation that pays for itself in the first week.&lt;/p&gt;

&lt;h3&gt;
  
  
  Get Started
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Product page&lt;/strong&gt;: &lt;a href="https://sharpapi.com/en/catalog/ai/accounting-finance/invoice-parser" rel="noopener noreferrer"&gt;SharpAPI Invoice Parser&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Detailed blog post with full response examples&lt;/strong&gt;: &lt;a href="https://sharpapi.com/en/blog/post/invoice-parsing-api-extract-structured-data-from-any-invoice" rel="noopener noreferrer"&gt;Invoice Parsing API - Extract Structured Data from Any Invoice&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everything runs on SOC 2 Type II certified infrastructure, so your invoice data is handled with the same security standards we maintain across all our client work.&lt;/p&gt;

&lt;p&gt;Got a specific invoice processing challenge? &lt;a href="https://a2zweb.co/contact" rel="noopener noreferrer"&gt;Talk to us&lt;/a&gt; - whether you need the API integrated into your existing systems or a full custom workflow built around it, that's literally what we do.&lt;/p&gt;

</description>
      <category>finance</category>
      <category>api</category>
      <category>parsing</category>
    </item>
    <item>
      <title>Invoice Parsing API - Extract Structured Data from Any Invoice</title>
      <dc:creator>Dawid Makowski</dc:creator>
      <pubDate>Sun, 22 Feb 2026 08:41:16 +0000</pubDate>
      <link>https://dev.to/makowskid/invoice-parsing-api-extract-structured-data-from-any-invoice-3ph7</link>
      <guid>https://dev.to/makowskid/invoice-parsing-api-extract-structured-data-from-any-invoice-3ph7</guid>
      <description>&lt;p&gt;Photo by &lt;a href="https://unsplash.com/@karepesinde?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Cht Gsml&lt;/a&gt; on &lt;a href="https://unsplash.com/photos/desk-with-papers-glasses-calculator-and-office-supplies-sW02MHv37yk?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What It Does
&lt;/h2&gt;

&lt;p&gt;Upload an invoice in any of 8 supported formats (DOC, DOCX, PDF, JPG, JPEG, PNG, TIFF, TIF), and the API extracts and structures the entire document into a comprehensive data object. We're talking seller and buyer details, full financial breakdowns with tax calculations, individual line items, payment terms, logistics info, e-invoice metadata, and reference numbers — all neatly organized and ready for your systems.&lt;/p&gt;

&lt;p&gt;And yes, it handles scanned invoices and flattened PDFs where the content is images rather than selectable text. Because in the real world, invoices aren't always pixel-perfect.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-World Use Cases
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Automated Accounts Payable
&lt;/h3&gt;

&lt;p&gt;Feed invoices directly into your AP workflow. The API extracts vendor details, amounts, due dates, and line items — eliminating manual data entry and reducing processing time from minutes per invoice to seconds.&lt;/p&gt;

&lt;h3&gt;
  
  
  ERP &amp;amp; Accounting System Integration
&lt;/h3&gt;

&lt;p&gt;Pipe structured invoice data straight into your ERP (SAP, Oracle, NetSuite, QuickBooks, Xero — you name it). No more reconciliation headaches, no more "which field maps to what" conversations. The data comes out clean and consistent every time.&lt;/p&gt;

&lt;h3&gt;
  
  
  Spend Analytics &amp;amp; Vendor Management
&lt;/h3&gt;

&lt;p&gt;Aggregate invoice data across your entire vendor base to spot spending trends, negotiate better terms, and identify cost-saving opportunities. When every invoice is structured data, building dashboards and running analyses becomes trivial.&lt;/p&gt;

&lt;h3&gt;
  
  
  Expense Management &amp;amp; Compliance
&lt;/h3&gt;

&lt;p&gt;Automate expense report validation by parsing receipts and invoices. Cross-reference extracted data against company policies, flag anomalies, and maintain a clean audit trail — all without human intervention.&lt;/p&gt;

&lt;h3&gt;
  
  
  Multi-Currency &amp;amp; Cross-Border Operations
&lt;/h3&gt;

&lt;p&gt;The API extracts currency information, exchange rates, VAT/GST/SST IDs, and country-specific tax details. Perfect for businesses operating across borders that need to handle invoices in multiple currencies and comply with regional tax requirements.&lt;/p&gt;

&lt;h3&gt;
  
  
  Invoice Verification &amp;amp; Fraud Detection
&lt;/h3&gt;

&lt;p&gt;Compare extracted invoice data against purchase orders, delivery receipts, and contracts. Automatically flag discrepancies in quantities, pricing, or vendor information before payments go out.&lt;/p&gt;

&lt;h2&gt;
  
  
  How It Works
&lt;/h2&gt;

&lt;p&gt;Like all SharpAPI endpoints, Invoice Parsing follows a simple two-step async pattern:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1:&lt;/strong&gt; Submit your invoice file via a POST request to &lt;code&gt;https://sharpapi.com/api/v1/finance/parse_invoice&lt;/code&gt;. You'll receive a job ID and status URL.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2:&lt;/strong&gt; Poll the status URL until the job completes. The result contains the full structured invoice data.&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;--location&lt;/span&gt; &lt;span class="s1"&gt;'https://sharpapi.com/api/v1/finance/parse_invoice'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'Accept: application/json'&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;"Authorization: Bearer YOUR_API_TOKEN"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--form&lt;/span&gt; &lt;span class="s1"&gt;'file=@"invoice.pdf"'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response includes over 100 individual data fields organized into logical sections: document metadata, invoice details, references, e-invoice data, seller/buyer information, financials with tax breakdowns, line items, payment terms, and logistics data.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Matters
&lt;/h2&gt;

&lt;p&gt;Manual invoice processing costs businesses an average of $15–$40 per invoice when you factor in labor, errors, and delays. At scale, that adds up fast. By automating the extraction step, you're not just saving time — you're eliminating the single biggest source of AP errors and freeing your team to focus on work that actually requires human judgment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get Started
&lt;/h2&gt;

&lt;p&gt;The Invoice Parsing endpoint is available now on all SharpAPI plans. Check out the details of &lt;a href="https://sharpapi.com/en/catalog/ai/accounting-finance/invoice-parser" rel="noopener noreferrer"&gt;Invoice Parsing&lt;/a&gt; for detailed request/response schemas, supported languages, and integration guides.&lt;/p&gt;

&lt;p&gt;Your invoices aren't going to parse themselves. Well, actually — now they will.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Integrate faster with SharpAPI SDKs.&lt;/strong&gt;&lt;br&gt;
Ready-to-use client packages for PHP, Laravel, Node.js, Python, Flutter, and .NET are available on GitHub.&lt;br&gt;
&lt;a href="https://github.com/orgs/sharpapi/repositories?q=invoice" rel="noopener noreferrer"&gt;Browse Invoice Parsing SDKs →&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>invoice</category>
      <category>parser</category>
      <category>aiparsing</category>
      <category>invoiceparsing</category>
    </item>
    <item>
      <title>Your Penetration Test Report Just Landed. Read This Before You Panic.</title>
      <dc:creator>Dawid Makowski</dc:creator>
      <pubDate>Tue, 17 Feb 2026 10:29:04 +0000</pubDate>
      <link>https://dev.to/makowskid/your-penetration-test-report-just-landed-read-this-before-you-panic-1a6m</link>
      <guid>https://dev.to/makowskid/your-penetration-test-report-just-landed-read-this-before-you-panic-1a6m</guid>
      <description>&lt;p&gt;I've watched the same movie play out too many times: a management team receives a penetration testing report, sees a wall of findings with scary-sounding names, and immediately assumes their platform is on fire.&lt;/p&gt;

&lt;p&gt;It's not on fire. It's almost never on fire.&lt;/p&gt;

&lt;p&gt;But the report sure makes it &lt;em&gt;look&lt;/em&gt; like it is. And that's the problem I keep running into - not with the platforms, but with how people read these reports.&lt;/p&gt;

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

&lt;p&gt;When you're a CTO or an external CTO-as-a-Service advisor, part of the job is translating between the world of security tooling and the world of business decision-making. These two worlds speak very different languages. Security tools speak in volumes of automated findings. Business leaders speak in risk, cost, and "should I be worried right now?"&lt;/p&gt;

&lt;p&gt;That gap is where the panic starts.&lt;/p&gt;

&lt;p&gt;Over the years I've learned to get ahead of it. Before every VAPT (Vulnerability Assessment and Penetration Testing) cycle, I walk my clients through what to expect from the results - what the findings actually mean, what's noise, and what deserves real attention. It's part education, part expectation management, and part gentle reminder that a 200-page PDF full of findings does not mean the sky is falling. Sometimes it just means the scanner was very thorough and a bit too enthusiastic.&lt;/p&gt;

&lt;p&gt;The goal is simple: give non-technical stakeholders the mental framework to read a VAPT report without losing sleep. Because the report is only half of the story. The other half - the part that actually matters - is interpreting those findings in the context of &lt;em&gt;your&lt;/em&gt; platform, &lt;em&gt;your&lt;/em&gt; architecture, and &lt;em&gt;your&lt;/em&gt; specific business requirements.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Anatomy of a VAPT Report (For Humans)
&lt;/h2&gt;

&lt;p&gt;Here's what most people don't realize about penetration testing: the raw output of any engagement is never the final verdict on your security. It's a starting point for analysis.&lt;/p&gt;

&lt;p&gt;VAPT teams rely on automated scanning tools to generate their initial findings. These tools are designed to cast an absurdly wide net. They flag anything that &lt;em&gt;could&lt;/em&gt; theoretically be a concern. And I mean &lt;em&gt;anything&lt;/em&gt;. Your OAuth integration with Google? Flagged. Your CDN serving static assets from a different domain? Flagged. A cookie that JavaScript can access because your entire framework was literally designed that way? You better believe that's flagged. Any open port on the server, even port 80 or 443? Yup, also flagged.&lt;/p&gt;

&lt;p&gt;This isn't a flaw in the process. It's how the process works. The tools are doing their job. The question is what happens next.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Quality Gap Nobody Talks About
&lt;/h2&gt;

&lt;p&gt;And here's where it gets interesting.&lt;/p&gt;

&lt;p&gt;Not all VAPT teams are created equal. In fact, there's a pretty dramatic quality spectrum, and where your team falls on it determines whether you receive a useful, contextualised security assessment or a PDF-shaped anxiety attack.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Budget-oriented teams&lt;/strong&gt; tend to optimise for volume. They run the tools, collect the output, and forward everything to the client with minimal filtering. The result? A report with dozens - sometimes hundreds - of findings, many of which are informational noise or outright false positives. It looks impressive. It fills a lot of pages. But it creates exactly the kind of alarm that derails productive conversations about actual security.&lt;/p&gt;

&lt;p&gt;I've seen reports where the same exact finding was listed separately for every URL on the platform. Same issue, same root cause, same "vulnerability" - just presented 147 times to make the PDF thicker.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;More experienced teams&lt;/strong&gt; - and yes, they typically cost more - invest significant effort in triaging their tool output before presenting it. They separate signal from noise. They tell you what actually matters and why. They cross-reference previous engagement results instead of re-investigating known behaviours from scratch. Their reports are shorter, more accurate, and infinitely more useful. You're paying for judgment, not just scanning hours.&lt;/p&gt;

&lt;h2&gt;
  
  
  Severity Levels: A Quick Decoder Ring
&lt;/h2&gt;

&lt;p&gt;Every VAPT report categorises findings by severity. Here's the practical translation:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Critical and High&lt;/strong&gt; - Stop what you're doing and fix these. These represent real, exploitable vulnerabilities. In a well-maintained platform with regular dependency updates, strong authentication, and proper encryption, these should be rare. If your report is full of them, you have a genuine problem. If it has zero, congratulations - that's the goal.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Medium and Low&lt;/strong&gt; - Read these with a calm mind. They often represent theoretical risks, hardening suggestions, or configuration preferences. Many are informational. Think of them as a security consultant saying "you &lt;em&gt;could&lt;/em&gt; also do this" rather than "your house is currently on fire."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Informational&lt;/strong&gt; - These are diagnostic notes. They describe how your platform behaves. They don't indicate risk. You can acknowledge them and move on.&lt;/p&gt;

&lt;p&gt;The number of findings in a report tells you almost nothing about how secure your platform is. A report with 150 findings and zero criticals is a dramatically better result than one with 5 findings and 2 criticals.&lt;/p&gt;

&lt;h2&gt;
  
  
  False Positives: The Uninvited Guests
&lt;/h2&gt;

&lt;p&gt;Every - and I mean &lt;em&gt;every&lt;/em&gt; - VAPT engagement produces false positives. These are findings that automated tools flag as potential issues but which, upon analysis, turn out to be expected framework behaviours, design decisions, or artefacts of the cloud infrastructure itself.&lt;/p&gt;

&lt;p&gt;In a recent engagement, we documented over 20 false positives across two reports. The cloud provider's own security infrastructure was triggering alerts during the scan - the scanning tools were essentially detecting the host's defence systems and reporting them as application vulnerabilities. That's like a home inspector flagging your alarm system as a security risk. Technically, something happened. Practically, it's the opposite of a problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  Context Is Everything
&lt;/h2&gt;

&lt;p&gt;If there's one thing I want people to take away from this, it's this: &lt;strong&gt;a VAPT report must always be read in the context of the specific platform it was conducted against.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Security is not a one-size-fits-all discipline. A finding that represents a genuine vulnerability on one platform could be an intentional design decision on another. Session tokens in URLs? Alarming - unless they're part of a standard OAuth handshake with a provider like Google or Twitter, in which case they're temporary, scoped, and exactly where they're supposed to be. Cross-domain script includes? Suspicious - unless they're loading Google's reCAPTCHA or your SSO integration, in which case they're essential.&lt;/p&gt;

&lt;p&gt;The report is half of the truth. The contextual analysis is the other half. Without both, you're making decisions based on incomplete information - and in my experience, those decisions tend to lean toward unnecessary panic and wasted remediation effort.&lt;/p&gt;

&lt;p&gt;If you have a VAPT cycle coming up, prepare your stakeholders before the report lands. It'll save you a week of damage-control conversations that didn't need to happen.&lt;/p&gt;

</description>
      <category>pentest</category>
      <category>security</category>
      <category>webdev</category>
    </item>
    <item>
      <title>CVMatchScore.com: A Real-World Showcase of Our Resume Match API</title>
      <dc:creator>Dawid Makowski</dc:creator>
      <pubDate>Fri, 09 Jan 2026 12:17:51 +0000</pubDate>
      <link>https://dev.to/makowskid/cvmatchscorecom-a-real-world-showcase-of-our-resume-match-api-1gio</link>
      <guid>https://dev.to/makowskid/cvmatchscorecom-a-real-world-showcase-of-our-resume-match-api-1gio</guid>
      <description>&lt;p&gt;&lt;em&gt;Photo by &lt;a href="https://unsplash.com/@helloimnik?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Nik&lt;/a&gt; on &lt;a href="https://unsplash.com/photos/black-pencil-on-paper-3xNn1zGvBwY?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditCopyText" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;We just launched a small thing that accidentally became useful.&lt;/p&gt;

&lt;p&gt;It’s called &lt;strong&gt;CV Match Score&lt;/strong&gt;:&lt;br&gt;
&lt;a href="https://cvmatchscore.com" rel="noopener noreferrer"&gt;https://cvmatchscore.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The backstory is simple. I kept getting the same question from people building HR tools and workflows:&lt;/p&gt;

&lt;p&gt;“Cool endpoint. But what does it actually look like when a real human uses it?”&lt;/p&gt;

&lt;p&gt;Fair. JSON is charming, but it doesn’t exactly spark joy.&lt;/p&gt;

&lt;p&gt;So we built a tiny, fully functional product that showcases our SharpAPI endpoint in the wild, under real conditions, with real inputs. No screenshots. No “imagine it works like this.” It just works.&lt;/p&gt;

&lt;p&gt;Behind the scenes, CV Match Score uses this SharpAPI capability:&lt;br&gt;
&lt;a href="https://sharpapi.com/en/catalog/ai/hr-tech/resume-cv-job-match-score" rel="noopener noreferrer"&gt;https://sharpapi.com/en/catalog/ai/hr-tech/resume-cv-job-match-score&lt;/a&gt;&lt;/p&gt;

&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%2Fsharpapi.com%2Fstorage%2F3756%2Fconversions%2FChatGPT-Image-1-maj-2025%2C-13_51_39-full_size.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%2Fsharpapi.com%2Fstorage%2F3756%2Fconversions%2FChatGPT-Image-1-maj-2025%2C-13_51_39-full_size.jpg" alt="Resume Matching" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What it does:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You upload a CV.&lt;/li&gt;
&lt;li&gt;You paste a job description.&lt;/li&gt;
&lt;li&gt;You get a structured match score with explanations that make sense to humans, not just to machines having a meeting about keywords.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Why we built it (beyond our obvious addiction to shipping):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Documentation tells you what an API returns.&lt;/li&gt;
&lt;li&gt;A working tool shows you what users &lt;em&gt;feel&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;And that’s the difference between “nice endpoint” and “this can be a feature inside my product next week.”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you’re building:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;HR tech products&lt;/li&gt;
&lt;li&gt;ATS features&lt;/li&gt;
&lt;li&gt;internal screening workflows&lt;/li&gt;
&lt;li&gt;recruitment automation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This tool is basically a fast, honest preview of what the endpoint enables when you put a UI in front of it.&lt;/p&gt;

&lt;p&gt;Try it, break it, tell us what’s missing!&lt;/p&gt;

</description>
      <category>hrtech</category>
      <category>cvparsing</category>
      <category>hrapi</category>
      <category>airesumeparser</category>
    </item>
    <item>
      <title>Fixing Spatie's Laravel ResponseCache to Respect Accept-Language</title>
      <dc:creator>Dawid Makowski</dc:creator>
      <pubDate>Thu, 21 Aug 2025 09:18:38 +0000</pubDate>
      <link>https://dev.to/makowskid/fixing-spaties-laravel-responsecache-to-respect-accept-language-17i6</link>
      <guid>https://dev.to/makowskid/fixing-spaties-laravel-responsecache-to-respect-accept-language-17i6</guid>
      <description>&lt;p&gt;I’ll be honest. Few things are more frustrating than solving a problem by reaching for a great package… only to realize the problem is still there. That was me last week, yelling at my API like it had just spoiled the finale of a Netflix show.&lt;/p&gt;

&lt;p&gt;I was using &lt;a href="https://github.com/spatie/laravel-responsecache" rel="noopener noreferrer"&gt;Spatie’s Laravel ResponseCache&lt;/a&gt; package. It’s rock solid, it works out of the box, and it’s built by people I trust. But here’s the kicker: I turned it on in production and suddenly my multilingual API was speaking one language only. The first request cached in English. Every Arabic request after that? Still English.&lt;/p&gt;

&lt;p&gt;So here’s what it took to fix it. I hope it will help some lost soul on the internet one day.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Photo by &lt;a href="https://unsplash.com/@douglasamarelo?utm_content=creditCopyText&amp;amp;utm_medium=referral&amp;amp;utm_source=unsplash" rel="noopener noreferrer"&gt;Douglas Lopes&lt;/a&gt; on &lt;a href="https://unsplash.com/photos/a-laptop-computer-sitting-on-top-of-a-wooden-desk-ehyV_XOZ4iA?utm_content=creditCopyText&amp;amp;utm_medium=referral&amp;amp;utm_source=unsplash" rel="noopener noreferrer"&gt;Unsplash&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;




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

&lt;p&gt;Spatie’s ResponseCache builds cache keys from the host, normalized URI, HTTP method, and a suffix (usually the user ID). That works fine most of the time, but it completely ignores request headers like &lt;code&gt;Accept-Language&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Which means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;First request with &lt;code&gt;Accept-Language: en&lt;/code&gt; → response cached in English&lt;/li&gt;
&lt;li&gt;Second request with &lt;code&gt;Accept-Language: ar&lt;/code&gt; → still gets the English version&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;My API became linguistically challenged.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Fix: Middleware That Cares About Language
&lt;/h2&gt;

&lt;p&gt;Spatie lets you influence the cache key by setting a per-request attribute called &lt;code&gt;responsecache.cacheNameSuffix&lt;/code&gt;. All we have to do is populate it with something that changes when the language changes.&lt;/p&gt;

&lt;p&gt;Here’s the middleware I ended up shipping:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;

&lt;span class="k"&gt;declare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;strict_types&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="kn"&gt;namespace&lt;/span&gt; &lt;span class="nn"&gt;App\Http\Middleware&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Closure&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Http\Request&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Support\Str&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SetResponseCacheSuffixFromLanguage&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Request&lt;/span&gt; &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;Closure&lt;/span&gt; &lt;span class="nv"&gt;$next&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Take the first language from the Accept-Language header&lt;/span&gt;
        &lt;span class="nv"&gt;$accept&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Str&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;header&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Accept-Language'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;before&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;','&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'_'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'-'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;value&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// Add user id if you want to separate caches per user&lt;/span&gt;
        &lt;span class="nv"&gt;$userId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="nv"&gt;$suffix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;implode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'|'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;array_filter&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nv"&gt;$userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$accept&lt;/span&gt;&lt;span class="p"&gt;]));&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$suffix&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;attributes&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'responsecache.cacheNameSuffix'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$suffix&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c1"&gt;// Optional: add a debug header so you can see what suffix is being used&lt;/span&gt;
        &lt;span class="nv"&gt;$response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$request&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$suffix&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'X-ResponseCache-Suffix'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;$suffix&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="nv"&gt;$response&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Registering the Middleware
&lt;/h2&gt;

&lt;p&gt;In Laravel 12, add it globally in &lt;code&gt;bootstrap/app.php&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;App\Http\Middleware\SetResponseCacheSuffixFromLanguage&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;use&lt;/span&gt; &lt;span class="nc"&gt;Illuminate\Foundation\Configuration\Middleware&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;Application&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;basePath&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dirname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;__DIR__&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;withMiddleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Middleware&lt;/span&gt; &lt;span class="nv"&gt;$middleware&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nv"&gt;$middleware&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;SetResponseCacheSuffixFromLanguage&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;class&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the cache key varies by &lt;code&gt;Accept-Language&lt;/code&gt; and you no longer serve Arabic users the English version of your API.&lt;/p&gt;




&lt;h2&gt;
  
  
  Adding the Vary Header
&lt;/h2&gt;

&lt;p&gt;If you’re running behind AWS ALB or a CDN, make sure your responses include &lt;code&gt;Vary: Accept-Language&lt;/code&gt;. Otherwise proxies and browsers might serve the wrong cached version.&lt;/p&gt;

&lt;p&gt;Just drop this into the middleware:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s1"&gt;'Vary'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nb"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'Vary'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;?:&lt;/span&gt; &lt;span class="s1"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt; &lt;span class="s1"&gt;', Accept-Language'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;', '&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Don’t Forget to Clear Old Cache
&lt;/h2&gt;

&lt;p&gt;The existing cache entries are language-agnostic, so clear them once:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;php artisan responsecache:clear
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  How to Prove It Works
&lt;/h2&gt;

&lt;p&gt;Run these two curls and check the header:&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;-H&lt;/span&gt; &lt;span class="s1"&gt;'Accept-Language: en'&lt;/span&gt; https://api.example.com/v1/pages &lt;span class="nt"&gt;-I&lt;/span&gt; | &lt;span class="nb"&gt;grep &lt;/span&gt;X-ResponseCache-Suffix
curl &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s1"&gt;'Accept-Language: ar'&lt;/span&gt; https://api.example.com/v1/pages &lt;span class="nt"&gt;-I&lt;/span&gt; | &lt;span class="nb"&gt;grep &lt;/span&gt;X-ResponseCache-Suffix
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see &lt;code&gt;en&lt;/code&gt; vs &lt;code&gt;ar&lt;/code&gt;. That’s your proof the cache is now language-aware.&lt;/p&gt;

&lt;p&gt;Will be chasing Spatie's team to add this to the package as well, fingers crossed!&lt;/p&gt;




&lt;p&gt;&lt;a href="https://dawidmakowski.com/en/" rel="noopener noreferrer"&gt;Check my personal blog&lt;/a&gt; for more tech-related content.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>laravel</category>
      <category>spatie</category>
    </item>
    <item>
      <title>Resume/CV &amp; Job Description Match Scoring Just Got an Upgrade</title>
      <dc:creator>Dawid Makowski</dc:creator>
      <pubDate>Thu, 01 May 2025 11:24:57 +0000</pubDate>
      <link>https://dev.to/makowskid/resumecv-job-description-match-scoring-just-got-an-upgrade-3bfi</link>
      <guid>https://dev.to/makowskid/resumecv-job-description-match-scoring-just-got-an-upgrade-3bfi</guid>
      <description>&lt;p&gt;All the API endpoint details are available here: &lt;a href="https://dev.to/en/catalog/ai/hr-tech/resume-cv-job-match-score"&gt;Resume/CV Job Match Score API&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What Problem Are We Solving?
&lt;/h2&gt;

&lt;p&gt;Let’s face it—traditional resume screening tools often rely on rigid keyword matching. They miss context, nuance, and real-life relevance. And manual screening? It’s time-consuming, inconsistent, and let’s be honest—no one enjoys it.&lt;/p&gt;

&lt;p&gt;Our new API brings &lt;strong&gt;AI-powered intelligence&lt;/strong&gt; to resume screening. Instead of just looking for the word “JavaScript,” it asks:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;How experienced is this person with JavaScript?&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Is that experience relevant to this specific role?&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Does their education, project history, or certifications back it up?&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In other words, it &lt;strong&gt;thinks like a recruiter&lt;/strong&gt;—but processes results in milliseconds.&lt;/p&gt;




&lt;h2&gt;
  
  
  How It Works
&lt;/h2&gt;

&lt;p&gt;Just upload a resume file (PDF, DOCX, TXT—you name it) and paste the job description. The API will handle the rest:  &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Automatically extract relevant data from the CV.
&lt;/li&gt;
&lt;li&gt;Compare it against the job requirements using 20+ AI-weighted scoring factors.
&lt;/li&gt;
&lt;li&gt;Return a structured JSON response with both &lt;strong&gt;match scores&lt;/strong&gt; and &lt;strong&gt;explanations&lt;/strong&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That’s it. You get an instant breakdown of how well a candidate fits your role—no manual guesswork.&lt;/p&gt;




&lt;h2&gt;
  
  
  Key Features
&lt;/h2&gt;

&lt;p&gt;✅ &lt;strong&gt;Real Matching Logic&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Goes beyond keywords by evaluating actual job experience, education, technical stacks, methodologies, and more.&lt;/p&gt;

&lt;p&gt;📊 &lt;strong&gt;20+ Compatibility Parameters&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Includes scores for skills, certifications, cultural fit, stability, location preferences, and more.&lt;/p&gt;

&lt;p&gt;💬 &lt;strong&gt;Built-In Explanation Engine&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Get reasoning behind the score—great for recruiters and hiring managers who want transparency.&lt;/p&gt;

&lt;p&gt;📡 &lt;strong&gt;RESTful API, Developer Friendly&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Supports multipart form uploads. You can integrate it into ATS platforms or use it in standalone workflows.&lt;/p&gt;

&lt;p&gt;🌍 &lt;strong&gt;Supports Localization&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Want explanations in French or Spanish? Just set the language parameter.&lt;/p&gt;




&lt;h2&gt;
  
  
  Sample Use Case
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;--location&lt;/span&gt; &lt;span class="s1"&gt;'https://sharpapi.com/api/v1/hr/resume_match_score'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--header&lt;/span&gt; &lt;span class="s1"&gt;'Accept: application/json'&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;"Authorization: Bearer YOUR_API_TOKEN"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--form&lt;/span&gt; &lt;span class="s1"&gt;'file=@"Resume.pdf"'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--form&lt;/span&gt; &lt;span class="s1"&gt;'content="We are hiring a backend developer skilled in Laravel and PostgreSQL..."'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
&lt;span class="nt"&gt;--form&lt;/span&gt; &lt;span class="s1"&gt;'language="English"'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Response Example
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"api_job_result"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2f17d9ef-dcbc-4521-9a20-6d9f41e58de8"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"attributes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"success"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"hr_resume_job_match_score"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"result"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"match_scores"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"overall_match"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;65&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"skills_match"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"experience_match"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"education_match"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"certifications_match"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"job_title_relevance"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;70&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"industry_experience_match"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;85&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"project_experience_match"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;75&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"technical_stack_match"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"methodologies_match"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"soft_skills_match"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;75&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"language_proficiency_match"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"location_preference_match"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"remote_work_flexibility"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;80&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"certifications_training_relevance"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"years_experience_weighting"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"recent_role_relevance"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"management_experience_match"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"cultural_fit_potential"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;70&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"stability_score"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;85&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="nl"&gt;"explanations"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"skills_match"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"The candidate has strong PHP and MySQL skills, which align well with the job requirements. However, specific mention of Laravel experience is missing."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"experience_match"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"The candidate has over 22 years of programming experience, which is highly relevant and exceeds the typical requirements for the role."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"education_match"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"No specific educational background is provided in the resume, making it impossible to assess alignment with job requirements."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"certifications_match"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"No certifications are listed in the resume, so alignment with any required certifications cannot be assessed."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="nl"&gt;"language_proficiency_match"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"The candidate has professional working proficiency in English, which matches the job requirement for English communication skills."&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Who Should Use This API?
&lt;/h2&gt;

&lt;p&gt;This API is ideal for:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;HR tech platforms&lt;/strong&gt; looking to boost their matching capabilities
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Recruitment agencies&lt;/strong&gt; automating pre-screening
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hiring managers&lt;/strong&gt; who want fast, data-backed insights
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ATS systems&lt;/strong&gt; needing a plug-and-play enhancement&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;This isn’t just another endpoint. It’s a shift in how we approach early-stage hiring decisions—automated, structured, and powered by AI.&lt;/p&gt;

&lt;p&gt;Try it. Plug it into your workflow. Let the machine do the matchwork.&lt;/p&gt;

&lt;p&gt;🧠 Smarter hires start here.  &lt;/p&gt;

&lt;p&gt;📌 &lt;a href="https://sharpapi.com/docs/hr/resume_match_score" rel="noopener noreferrer"&gt;Explore the API endpoint Docs →&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;📌 &lt;a href="https://github.com/sharpapi/laravel-resume-job-match-score" rel="noopener noreferrer"&gt;AI Resume &amp;amp; Job Description Matching for Laravel&lt;/a&gt;&lt;/p&gt;

</description>
      <category>hrtech</category>
      <category>cvparsing</category>
      <category>aiinrecruitment</category>
      <category>hrapi</category>
    </item>
    <item>
      <title>Vibe Coding Is Fun—But Vibe Refactoring Pays the Bills</title>
      <dc:creator>Dawid Makowski</dc:creator>
      <pubDate>Sat, 26 Apr 2025 14:33:42 +0000</pubDate>
      <link>https://dev.to/makowskid/vibe-coding-is-fun-but-vibe-refactoring-pays-the-bills-1lo0</link>
      <guid>https://dev.to/makowskid/vibe-coding-is-fun-but-vibe-refactoring-pays-the-bills-1lo0</guid>
      <description>&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/M8WdXRuzHvU"&gt;
  &lt;/iframe&gt;
&lt;br&gt;
There’s a lot of hype about &lt;strong&gt;vibe coding&lt;/strong&gt;—that moment when caffeine hits, your playlist slaps, and you hammer out code like a jazz drummer sprinting through a solo. It’s exhilarating, but relying on that adrenaline burst is like funding your retirement with scratch-offs.&lt;/p&gt;

&lt;p&gt;So let’s flip the script to something that actually compounds: &lt;strong&gt;vibe refactoring&lt;/strong&gt;. Same spontaneous energy, but aimed at shrinking technical debt and sharpening your architecture instead of amping up the commit count.&lt;/p&gt;




&lt;h3&gt;
  
  
  A Quick Setup
&lt;/h3&gt;

&lt;p&gt;Block &lt;strong&gt;15–20 minutes&lt;/strong&gt; on your calendar. No ticket, no KPI, no “reduce cyclomatic complexity by 13.7 %” targets. Just a promise to poke around your codebase with beginner eyes.&lt;/p&gt;




&lt;h3&gt;
  
  
  Step-by-Step (a.k.a. How the Magic Happens)
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Open the IDE and wander&lt;/strong&gt;
Pretend you cloned this repo yesterday. Warnings and TODOs suddenly look less like background noise and more like neon signs that say &lt;em&gt;“Fix me, champ.”&lt;/em&gt; Clear a few of those—you’ll feel lighter already.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Let the IDE nag you&lt;/strong&gt;
Hover over the yellow squiggles. Remove the unused imports, tame the long methods, rename the variable that’s secretly been haunting you since 2019. Each micro-win is a quick dopamine hit.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Run &lt;code&gt;[repomix](https://repomix.com/)&lt;/code&gt; (formerly &lt;em&gt;repopack&lt;/em&gt;)&lt;/strong&gt;
This bundles context so large-language models can actually understand your project’s ecosystem instead of hallucinating a new service layer “for funsies.”&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Chat with an LLM&lt;/strong&gt;
Drop a knotty function into your favorite model and ask:
&lt;em&gt;“Any cleaner way to handle this?”&lt;/em&gt; or
&lt;em&gt;“Spot an N+1 query?”&lt;/em&gt; or
&lt;em&gt;“Got an indexing tip for this desperate JOIN?”&lt;/em&gt;
It’s brutally honest rubber-duck debugging—and it never rolls its eyes.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Follow the rabbit hole&lt;/strong&gt;
A “quick” fix often blossoms into a &lt;strong&gt;two-hour&lt;/strong&gt; code spa. Let it. Today’s detour is tomorrow’s velocity boost.&lt;/li&gt;
&lt;/ol&gt;




&lt;h3&gt;
  
  
  What You’ll Gain (Besides a Self-Esteem Spike)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Compounding quality&lt;/strong&gt;: Tiny weekly tweaks snowball into major stability over months.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Faster deployments&lt;/strong&gt;: Fewer regressions means release day stops feeling like Russian roulette.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Happier teammates&lt;/strong&gt;: A clean codebase is onboarding heaven—no more haunted-house tours for new hires.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Satisfied customers&lt;/strong&gt;: Snappier queries and smoother UX, even if they can’t pinpoint why things suddenly &lt;em&gt;feel&lt;/em&gt; better.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  Keep It Light, Keep It Weekly
&lt;/h3&gt;

&lt;p&gt;No formal goals, no pressure. Cue your favorite playlist, celebrate micro-wins in Slack (GIFs encouraged), and rotate refactor buddies so fresh eyes stay fresh. This is maintenance rebranded as exploration—curiosity, but profitable.&lt;/p&gt;




&lt;h3&gt;
  
  
  Final Thought
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Vibe coding&lt;/strong&gt; gives you adrenaline.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Vibe refactoring&lt;/strong&gt; gives you longevity.&lt;/p&gt;

&lt;p&gt;Block the time, wander through the code, and watch the compound interest kick in. Do it once and you’ll feel lighter; do it every week and you’ll wonder how you ever shipped without it.&lt;/p&gt;

</description>
      <category>coding</category>
      <category>software</category>
      <category>llm</category>
      <category>programming</category>
    </item>
    <item>
      <title>ChatGPT is Bullshit: Why Your AI Buddy Might Be Bluffing</title>
      <dc:creator>Dawid Makowski</dc:creator>
      <pubDate>Thu, 27 Mar 2025 07:27:17 +0000</pubDate>
      <link>https://dev.to/makowskid/chatgpt-is-bullshit-why-your-ai-buddy-might-be-bluffing-11oe</link>
      <guid>https://dev.to/makowskid/chatgpt-is-bullshit-why-your-ai-buddy-might-be-bluffing-11oe</guid>
      <description>&lt;p&gt;Alright, folks, let’s chat about why ChatGPT sometimes sounds like it’s confidently bluffing its way through a poker game it barely understands. We’re talking about a study by Michael Townsen Hicks, James Humphries, and Joe Slater titled “ChatGPT is Bullshit.” Yes, that’s the actual title. Let’s dig in.&lt;/p&gt;

&lt;h2&gt;
  
  
  It’s Not Lying, It’s Just BS-ing
&lt;/h2&gt;

&lt;p&gt;The study suggests that ChatGPT isn’t lying or hallucinating—it’s simply bullshitting. It doesn’t care about being right; it cares about sounding like it knows what it’s talking about. Think of it as your one friend who’s always got a factoid, but you’re 80% sure they’re making it up as they go.&lt;/p&gt;

&lt;h2&gt;
  
  
  Truth? Who Needs It?
&lt;/h2&gt;

&lt;p&gt;ChatGPT’s job isn’t to deliver the truth; it’s to deliver something that feels truthy. It’s all about the vibe, not the validity. Like when you’re in an argument, and you drop a line that just sounds like it should win the debate, even if you have no idea what you’re saying.&lt;/p&gt;

&lt;h2&gt;
  
  
  It’s a Probability Party
&lt;/h2&gt;

&lt;p&gt;Behind the scenes, ChatGPT is basically just guessing which word should come next based on probabilities. It’s not checking facts—it’s rolling the dice. Sometimes it’s a winning hand, and other times… well, you get a sentence that sounds like it came from the Twilight Zone.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why It Matters
&lt;/h2&gt;

&lt;p&gt;The authors argue that calling ChatGPT’s mistakes “hallucinations” is misleading. It’s not seeing things that aren’t there—it’s just spouting out BS because it doesn’t actually care if what it’s saying is true or not. It’s like that person who can’t admit they’re wrong, so they just keep doubling down.&lt;/p&gt;

&lt;h2&gt;
  
  
  BS Isn’t Harmless
&lt;/h2&gt;

&lt;p&gt;Here’s the kicker: This kind of BS isn’t just harmless fun. When people mistake ChatGPT’s confident nonsense for actual truth, things can go sideways—fast. Just ask the lawyer who cited fake cases ChatGPT made up. Oops.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thought
&lt;/h2&gt;

&lt;p&gt;So, next time ChatGPT tells you something wild, remember: It’s not lying, it’s just a smooth talker who’s really good at pretending to know what’s up. Let’s keep that in mind when we ask it for advice.&lt;/p&gt;

&lt;p&gt;Check out the full study here for the juicy details: &lt;a href="https://link.springer.com/article/10.1007/s10676-024-09775-5" rel="noopener noreferrer"&gt;ChatGPT is Bullshit&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dawidmakowski.com/en/2024/08/chatgpt-is-bullshit-why-your-ai-buddy-might-be-bluffing/" rel="noopener noreferrer"&gt;Source&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>coding</category>
      <category>general</category>
      <category>internet</category>
    </item>
  </channel>
</rss>
