<?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: Piece moto occasion</title>
    <description>The latest articles on DEV Community by Piece moto occasion (@piecemotooccasion_).</description>
    <link>https://dev.to/piecemotooccasion_</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4136338%2F2d230357-7e34-40da-8e8d-36aab1c32e79.webp</url>
      <title>DEV Community: Piece moto occasion</title>
      <link>https://dev.to/piecemotooccasion_</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/piecemotooccasion_"/>
    <language>en</language>
    <item>
      <title>One API client, seven registries: what actually broke</title>
      <dc:creator>Piece moto occasion</dc:creator>
      <pubDate>Mon, 21 Sep 2026 19:17:27 +0000</pubDate>
      <link>https://dev.to/piecemotooccasion_/one-api-client-seven-registries-what-actually-broke-502e</link>
      <guid>https://dev.to/piecemotooccasion_/one-api-client-seven-registries-what-actually-broke-502e</guid>
      <description>&lt;p&gt;We run a small French marketplace for used motorcycle parts. Sellers are scrapyards, and scrapyards have every stock system imaginable: PrestaShop, Odoo, Dolibarr, an Excel file, a nephew with a Python script. So our seller API needed clients in whatever they already use.&lt;/p&gt;

&lt;p&gt;Over one week we published the same client on PyPI, npm, JSR, RubyGems, NuGet, Docker Hub and the Snap Store. The code was the easy part. Here is what each registry actually demanded, and what we got wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  The API is deliberately tiny
&lt;/h2&gt;

&lt;p&gt;Seven endpoints, one Bearer token, one webhook.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GET    /moi                          account, status, commission
GET    /produits                     your parts
PUT    /produits                     create or update, 200 per call
PATCH  /produits/{ref}/stock         {"stock": 3}
DELETE /produits/{ref}               withdraw (stock 0, never deleted)
GET    /commandes?depuis=YYYY-MM-DD  paid orders with shipping address
POST   /commandes/{id}/expedier      carrier + tracking number
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two design decisions shaped every client:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The seller's own SKU is the identifier.&lt;/strong&gt; &lt;code&gt;PUT /produits&lt;/code&gt; with a known reference updates; an unknown one creates. Clients never need to store our IDs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Batches of 200, and a refused row does not fail the batch.&lt;/strong&gt; The response carries &lt;code&gt;produits&lt;/code&gt; (accepted) and &lt;code&gt;erreurs&lt;/code&gt; (refused, with a reason). A thousand rows is five calls, and one bad price doesn't block 999 good parts.&lt;/p&gt;

&lt;p&gt;The webhook is signed: &lt;code&gt;X-Pmo-Signature: sha256=&amp;lt;HMAC-SHA256 of the raw body, key = your token&amp;gt;&lt;/code&gt;. Every client ships a verifier, and every verifier compares in constant time.&lt;/p&gt;

&lt;h2&gt;
  
  
  What each registry wanted
&lt;/h2&gt;

&lt;h3&gt;
  
  
  PyPI — publish from CI, no token anywhere
&lt;/h3&gt;

&lt;p&gt;PyPI's trusted publishing is the nicest flow of the seven. You register &lt;code&gt;owner/repo&lt;/code&gt;, the workflow filename and an environment name once; after that a tagged release publishes with no secret stored anywhere.&lt;/p&gt;

&lt;p&gt;Two traps:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The workflow filename is part of the identity.&lt;/strong&gt; We registered &lt;code&gt;publier-pypi.yml&lt;/code&gt;, then renamed the file. Every publish failed with &lt;code&gt;invalid-publisher&lt;/code&gt; until the names matched again.&lt;/li&gt;
&lt;li&gt;The form for a project that doesn't exist yet is the &lt;em&gt;pending publisher&lt;/em&gt; form, on your account page — not on the project page, which doesn't exist.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We also learned that &lt;code&gt;pip&lt;/code&gt; on macOS 26 is currently broken for some Python builds (&lt;code&gt;platform.mac_ver()&lt;/code&gt; returns an empty string and &lt;code&gt;truststore&lt;/code&gt; crashes). Build in CI, not on the laptop.&lt;/p&gt;

&lt;h3&gt;
  
  
  npm — 2FA is mandatory now
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;npm publish&lt;/code&gt; returned 403 until the account had two-factor auth. A passkey works: &lt;code&gt;npm publish&lt;/code&gt; then opens a browser to confirm instead of asking for &lt;code&gt;--otp&lt;/code&gt;. No token juggling.&lt;/p&gt;

&lt;p&gt;We published two packages: the client, and an &lt;a href="https://www.npmjs.com/package/n8n-nodes-piecemotooccasion" rel="noopener noreferrer"&gt;n8n community node&lt;/a&gt;. n8n lists any npm package with the &lt;code&gt;n8n-community-node-package&lt;/code&gt; keyword — the cheapest "integration marketplace" listing we found, with no user-count threshold unlike Zapier.&lt;/p&gt;

&lt;h3&gt;
  
  
  JSR — write TypeScript, or it won't document you
&lt;/h3&gt;

&lt;p&gt;JSR's "slow types" check refuses to generate docs for an untyped JavaScript API. So the JSR package is a separate TypeScript port, with one useful constraint: no Node built-ins. We replaced &lt;code&gt;node:crypto&lt;/code&gt; with &lt;code&gt;crypto.subtle&lt;/code&gt;, which made the same file run on Deno, Node 18+, Bun, Cloudflare Workers and in a browser.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;signatureValide&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;corps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nb"&gt;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;entete&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&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="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;entete&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;octets&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;corps&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TextEncoder&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;corps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;corps&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subtle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;importKey&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;raw&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;TextEncoder&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="err"&gt;#&lt;/span&gt;&lt;span class="nx"&gt;jeton&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;HMAC&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;hash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SHA-256&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sign&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;empreinte&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Uint8Array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subtle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;HMAC&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;cle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;octets&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;attendu&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sha256=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;empreinte&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;o&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;o&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;padStart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;""&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="nx"&gt;attendu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;entete&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;difference&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;attendu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;difference&lt;/span&gt; &lt;span class="o"&gt;|=&lt;/span&gt; &lt;span class="nx"&gt;attendu&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;charCodeAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt; &lt;span class="nx"&gt;entete&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;charCodeAt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;difference&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;jsr publish&lt;/code&gt; refuses to run from a dirty git tree, even when the dirt is in a parent directory unrelated to the package. &lt;code&gt;--allow-dirty&lt;/code&gt; is fine when you know why.&lt;/p&gt;

&lt;h3&gt;
  
  
  RubyGems — zero dependencies, and one missing function
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;net/http&lt;/code&gt;, &lt;code&gt;json&lt;/code&gt;, &lt;code&gt;openssl&lt;/code&gt;: the gem has no runtime dependency at all. One surprise: &lt;code&gt;OpenSSL.fixed_length_secure_compare&lt;/code&gt; only exists from Ruby 2.7, and &lt;code&gt;OpenSSL.secure_compare&lt;/code&gt; isn't everywhere either. Four lines of our own constant-time compare removed the version question.&lt;/p&gt;

&lt;h3&gt;
  
  
  NuGet — the URI class lies to you
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;Uri.EscapeDataString("REF /1")&lt;/code&gt; correctly gives &lt;code&gt;REF%20%2F1&lt;/code&gt;. Then &lt;code&gt;new Uri(...)&lt;/code&gt; displays the path with the space decoded again, and our test asserted on &lt;code&gt;ToString()&lt;/code&gt;. Panic, then a socket listener to see the real request line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="nf"&gt;PATCH&lt;/span&gt; &lt;span class="nn"&gt;/api/v1/produits/REF%20%2F1/stock&lt;/span&gt; &lt;span class="k"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="m"&gt;1.1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The wire was right all along; only the string representation lied. Test what leaves the machine, not what a getter returns.&lt;/p&gt;

&lt;h3&gt;
  
  
  Docker Hub — the only one whose links are followed
&lt;/h3&gt;

&lt;p&gt;We checked the HTML of package pages on each registry. PyPI, RubyGems, WordPress.org, Drupal.org, Packagist and GitHub all mark outbound links &lt;code&gt;rel="nofollow"&lt;/code&gt;. Docker Hub renders README links with &lt;code&gt;rel="noopener noreferrer"&lt;/code&gt; — no &lt;code&gt;nofollow&lt;/code&gt; — and serves the README server-side. If you care about that sort of thing, it's the one registry where the description page is worth writing properly.&lt;/p&gt;

&lt;p&gt;The image itself is trivial: a two-stage build that installs the PyPI package into a venv, then copies the venv into &lt;code&gt;python:3.12-alpine&lt;/code&gt;. 101 MB instead of 225 with &lt;code&gt;-slim&lt;/code&gt;, because our only dependency is &lt;code&gt;requests&lt;/code&gt;, which needs no compiler — the usual reason to avoid Alpine doesn't apply. Runs as uid 10001, token via environment variable, never on the command line where &lt;code&gt;docker ps&lt;/code&gt; would show it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Snap Store — you cannot build on a Mac
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;snapcraft&lt;/code&gt; installs on macOS but cannot produce a snap there. The path that works: push the &lt;code&gt;snapcraft.yaml&lt;/code&gt; to a public git repo (GitLab is fine), import it into Launchpad, create a snap recipe, tick amd64 + arm64, tick "automatically upload to store". Launchpad builds both architectures in about thirty minutes and pushes to the stable channel.&lt;/p&gt;

&lt;p&gt;Snapcraft.io also renders followed links, and the store listing lets you attach a verified website. We measured seven followed links from our listing page.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one thing every client shares
&lt;/h2&gt;

&lt;p&gt;Whatever the language, &lt;code&gt;envoyer()&lt;/code&gt; takes any iterable, slices it by 200, and returns accepted and refused rows separately. And every verifier works on the &lt;strong&gt;raw&lt;/strong&gt; request body: re-serializing JSON changes bytes, and the signature with them. Both rules were written once in the API and copied into all seven READMEs, because the first support question would otherwise have been "why does my signature not match".&lt;/p&gt;

&lt;h2&gt;
  
  
  Where they are
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Python: &lt;code&gt;pip install piecemotooccasion&lt;/code&gt; — includes the &lt;code&gt;pmo&lt;/code&gt; CLI&lt;/li&gt;
&lt;li&gt;JavaScript: &lt;code&gt;npm i piecemotooccasion&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;TypeScript / Deno: &lt;code&gt;jsr add @piecemotooccasion/api&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Ruby: &lt;code&gt;gem install piecemotooccasion&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;n8n: &lt;code&gt;n8n-nodes-piecemotooccasion&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Docker: &lt;code&gt;docker run tonydevweb/pmo envoyer catalogue.csv&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Linux: &lt;code&gt;snap install piecemotooccasion&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;API reference: &lt;a href="https://piecemotooccasion.eu/extensions/api" rel="noopener noreferrer"&gt;https://piecemotooccasion.eu/extensions/api&lt;/a&gt;&lt;/p&gt;

</description>
      <category>api</category>
      <category>python</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
