<?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: Bartosz Kuć</title>
    <description>The latest articles on DEV Community by Bartosz Kuć (@bartoszkuc).</description>
    <link>https://dev.to/bartoszkuc</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4090240%2Fff85ca6a-70e6-4f61-97cb-9bccfa369238.jpg</url>
      <title>DEV Community: Bartosz Kuć</title>
      <link>https://dev.to/bartoszkuc</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/bartoszkuc"/>
    <language>en</language>
    <item>
      <title>I packaged Polish company verification as a Claude Skill</title>
      <dc:creator>Bartosz Kuć</dc:creator>
      <pubDate>Mon, 24 Aug 2026 10:32:32 +0000</pubDate>
      <link>https://dev.to/bartoszkuc/i-packaged-polish-company-verification-as-a-claude-skill-47im</link>
      <guid>https://dev.to/bartoszkuc/i-packaged-polish-company-verification-as-a-claude-skill-47im</guid>
      <description>&lt;p&gt;If you use Claude Code or the Claude apps and you ever deal with Polish companies, here is a small thing that saves a surprising amount of time: a Skill that lets Claude verify a company by its tax ID, check whether it is a registered VAT payer, look up its bank account on the government white list, pull its court-register data, and validate an EU VAT number. No API key, no signup, nothing to run beyond ordinary HTTP.&lt;/p&gt;

&lt;p&gt;I published it as an open Agent Skill. This post is how it works and how to install it.&lt;/p&gt;

&lt;p&gt;Full disclosure up front: the API behind it, &lt;a href="https://skanfirmy.pl" rel="noopener noreferrer"&gt;skanfirmy.pl&lt;/a&gt;, is my own project. It is free and keyless, and I built the Skill so agents can reach it as easily as a person reaches the website.&lt;/p&gt;

&lt;h2&gt;
  
  
  What an Agent Skill actually is
&lt;/h2&gt;

&lt;p&gt;A Skill is just a folder with a &lt;code&gt;SKILL.md&lt;/code&gt; file: a bit of YAML frontmatter (a name and a description) and a markdown body with instructions. Claude reads the description, and when a task matches, it loads the body and follows it. There is no code to execute and no dependency to install. It is closer to a well written runbook than to a plugin.&lt;/p&gt;

&lt;p&gt;That format is portable. The same &lt;code&gt;SKILL.md&lt;/code&gt; works in Claude Code, in the Claude apps, and through the Agent SDK.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install it
&lt;/h2&gt;

&lt;p&gt;Drop the folder into a place Claude reads skills from:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/bartosz-kuc/skanfirmy-mcp

&lt;span class="c"&gt;# personal skills, all projects&lt;/span&gt;
&lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; ~/.claude/skills
&lt;span class="nb"&gt;cp&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; skanfirmy-mcp/skill ~/.claude/skills/verify-polish-company
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Restart Claude Code and you are done. The Skill loads on its own when a request matches, so you do not have to call it by name.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use it
&lt;/h2&gt;

&lt;p&gt;Now you can just ask in plain language:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;is NIP 5260250995 an active VAT payer, and what is on its white list?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Claude picks up the Skill and runs the right check. Under the hood it is a single GET:&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="s2"&gt;"https://skanfirmy.pl/nip/5260250995?format=json"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That returns the VAT status (active, exempt, or not registered), the company details from the VAT register, and the bank accounts that sit on the official white list ("Biała Lista"). Those two facts, VAT status and account match, are the ones that decide whether a Polish B2B payment is safe to deduct, so they are worth getting right.&lt;/p&gt;

&lt;p&gt;The Skill knows about the other registers too:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;GET /regon/{nip}&lt;/code&gt; returns REGON registry data from the statistics office, and it covers sole traders, who are not in the court register.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GET /nip/{nip}&lt;/code&gt; also folds in the KRS court record (legal form, capital, address, representation).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GET /vies/{country}/{number}&lt;/code&gt; validates an EU VAT number through the European Commission's VIES service.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everything is a plain GET that answers JSON when you add &lt;code&gt;?format=json&lt;/code&gt;. No key, no account, no daily quota.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a Skill and not just docs
&lt;/h2&gt;

&lt;p&gt;You could paste the endpoint list into a prompt every time. The Skill does three things that a one off paste does not:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It teaches Claude &lt;em&gt;when&lt;/em&gt; to reach for these checks, so it triggers on the real intent ("verify this contractor") instead of waiting to be told the URL.&lt;/li&gt;
&lt;li&gt;It picks the right endpoint. Sole trader? That is &lt;code&gt;/regon&lt;/code&gt;, because the court register does not cover them. Foreign VAT number? That is VIES. It encodes those small decisions so you do not have to.&lt;/li&gt;
&lt;li&gt;It names the government literals to key off, like the VAT status value, instead of a translated label that would break branching.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;There is also an MCP server at &lt;code&gt;https://skanfirmy.pl/mcp&lt;/code&gt; for agents that speak the Model Context Protocol, with the same checks as tools and the same no-key rule. The Skill points at it, but you do not need it for the REST path.&lt;/p&gt;

&lt;h2&gt;
  
  
  The honest part
&lt;/h2&gt;

&lt;p&gt;The data comes straight from official sources: the Ministry of Finance for the VAT white list, the Ministry of Justice for the court register, the statistics office (GUS) for REGON, and the European Commission for VIES. skanfirmy.pl is an independent tool, not an official government API, and it just exposes those registers in one place with one request instead of four.&lt;/p&gt;

&lt;p&gt;The Skill is MIT licensed and lives here: &lt;a href="https://github.com/bartosz-kuc/skanfirmy-mcp/tree/main/skill" rel="noopener noreferrer"&gt;github.com/bartosz-kuc/skanfirmy-mcp&lt;/a&gt;. If you wire company checks into agent workflows, I would genuinely like to hear what is missing.&lt;/p&gt;

</description>
      <category>claude</category>
      <category>ai</category>
      <category>mcp</category>
      <category>poland</category>
    </item>
    <item>
      <title>Checking Polish companies from code: VAT, KRS, REGON, EU VAT (REST + Python + MCP)</title>
      <dc:creator>Bartosz Kuć</dc:creator>
      <pubDate>Sun, 23 Aug 2026 18:18:20 +0000</pubDate>
      <link>https://dev.to/bartoszkuc/checking-polish-companies-from-code-vat-krs-regon-eu-vat-rest-python-mcp-2pkc</link>
      <guid>https://dev.to/bartoszkuc/checking-polish-companies-from-code-vat-krs-regon-eu-vat-rest-python-mcp-2pkc</guid>
      <description>&lt;p&gt;If you invoice or onboard Polish companies, sooner or later you have to check two dull things that turn out to matter a lot: is this company actually a registered VAT payer, and is the bank account they gave you the one that's on the government's official white list ("Biała Lista")? Both of those affect whether you can deduct the cost and reclaim VAT, so it's not really optional.&lt;/p&gt;

&lt;p&gt;The annoying part is that the data lives in four different places: the Ministry of Finance, the KRS court register, GUS (the stats office), and the EU's VIES service. Each one has its own API and its own quirks.&lt;/p&gt;

&lt;p&gt;I got tired of gluing those together every time, so I wrapped them behind a few plain HTTP calls that return JSON. Full disclosure: &lt;a href="https://skanfirmy.pl" rel="noopener noreferrer"&gt;skanfirmy.pl&lt;/a&gt; is mine. It's free, no key, no signup, and the web layer runs client-side with no tracking. Here's how you'd actually use it.&lt;/p&gt;

&lt;h2&gt;
  
  
  REST: one GET, one JSON
&lt;/h2&gt;

&lt;p&gt;Cheapest thing you can do is check a NIP (the tax ID):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl https://skanfirmy.pl/nip/5260250995
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You get back the VAT status (active, exempt, or not registered), the company details from the VAT register, and the accounts sitting on the white list.&lt;/p&gt;

&lt;p&gt;The paths:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;GET /nip/{nip}&lt;/code&gt; gives VAT status + white-list data for one NIP&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GET /nips/{list}&lt;/code&gt; takes several NIPs at once (comma-separated)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GET /regon/{nip}&lt;/code&gt; returns data from the REGON register (GUS)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GET /vies/{country}/{number}&lt;/code&gt; validates an EU VAT number, e.g. &lt;code&gt;/vies/DE/811128135&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It's a plain GET that returns JSON, so it drops into anything that can make an HTTP request: a cron job, a lambda, a CI step, whatever.&lt;/p&gt;

&lt;p&gt;I wrote up the REGON/GUS endpoint on its own page, with examples in a few languages and how it compares to the GUS BIR SOAP service: &lt;a href="https://skanfirmy.pl/api-gus" rel="noopener noreferrer"&gt;Polish company data from GUS (REGON) by NIP&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Python
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;requests&lt;/code&gt; and a few lines. This one raises if the company isn't an active VAT payer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check_vat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nip&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://skanfirmy.pl/nip/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;nip&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;vatStatus&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;status&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Czynny&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;# status comes back in Polish; compare against the raw value
&lt;/span&gt;        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NIP &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;nip&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: VAT status = &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="si"&gt;!r}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;

&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;check_vat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;5260250995&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;White-list accounts:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;accountNumbers&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One thing that bit me: the status strings from the Ministry come back in Polish (&lt;code&gt;Czynny&lt;/code&gt; = active, &lt;code&gt;Zwolniony&lt;/code&gt; = exempt). Compare against the original string, and don't translate before you branch on it, or the check silently breaks the day someone flips the UI language.&lt;/p&gt;

&lt;p&gt;Got a big list to run? Use &lt;code&gt;GET /nips/{list}&lt;/code&gt;, or the &lt;a href="https://skanfirmy.pl/bulk" rel="noopener noreferrer"&gt;/bulk&lt;/a&gt; page if you just need a CSV once.&lt;/p&gt;

&lt;h2&gt;
  
  
  MCP (for agents)
&lt;/h2&gt;

&lt;p&gt;This is the part I actually built the thing for. There's an MCP server at &lt;code&gt;https://skanfirmy.pl/mcp&lt;/code&gt; with 9 tools and no API key, so an agent (say, an accounting assistant) can run the same NIP check a human would click through.&lt;/p&gt;

&lt;p&gt;JSON-RPC 2.0 over POST. Calling a tool:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST https://skanfirmy.pl/mcp &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/call",
    "params": {
      "name": "sprawdz_nip",
      "arguments": { "nip": "5260250995" }
    }
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;tools/list&lt;/code&gt; gives you all of them with their input schemas. If your agent only speaks REST, there's an &lt;code&gt;llms.txt&lt;/code&gt; at &lt;code&gt;https://skanfirmy.pl/llms.txt&lt;/code&gt; describing the endpoints in a model-readable way. And if you need more than the Polish registers, I keep a sister catalog of public APIs aimed at agents over at &lt;a href="https://otwarteapi.pl" rel="noopener noreferrer"&gt;otwarteapi.pl&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  When one check isn't enough
&lt;/h2&gt;

&lt;p&gt;VAT status and white-list accounts change, sometimes day to day, and a one-off check won't tell you when that happens. So there's &lt;a href="https://skanfirmy.pl/monitoring" rel="noopener noreferrer"&gt;/monitoring&lt;/a&gt;: it diffs daily and pings you over an HMAC-signed webhook when something moves. You verify the signature on your end and react, instead of polling the registers in a loop.&lt;/p&gt;

&lt;h2&gt;
  
  
  That's basically it
&lt;/h2&gt;

&lt;p&gt;curl for a quick look, &lt;code&gt;requests&lt;/code&gt; to put it in code, MCP if an agent is doing the checking. All JSON, no signup, no key. (It's bilingual too: English pages live under &lt;code&gt;/en/&lt;/code&gt;, and the REST/MCP endpoints don't care about language either way.)&lt;/p&gt;

&lt;p&gt;If you're already solving this differently, especially the white-list account matching, I'd genuinely like to hear how. That part is fiddlier than it looks.&lt;/p&gt;

</description>
      <category>poland</category>
      <category>api</category>
      <category>mcp</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
