<?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: Ege</title>
    <description>The latest articles on DEV Community by Ege (@csecompat).</description>
    <link>https://dev.to/csecompat</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%2F4150719%2F5a50e919-368c-462b-8082-3276b9a8e7b9.jpeg</url>
      <title>DEV Community: Ege</title>
      <link>https://dev.to/csecompat</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/csecompat"/>
    <language>en</language>
    <item>
      <title>Google's Custom Search API shuts down Jan 1. Migrate by changing one URL.</title>
      <dc:creator>Ege</dc:creator>
      <pubDate>Tue, 29 Sep 2026 18:06:51 +0000</pubDate>
      <link>https://dev.to/csecompat/googles-custom-search-api-shuts-down-jan-1-migrate-by-changing-one-url-59c4</link>
      <guid>https://dev.to/csecompat/googles-custom-search-api-shuts-down-jan-1-migrate-by-changing-one-url-59c4</guid>
      <description>&lt;p&gt;If you have code that calls &lt;code&gt;https://www.googleapis.com/customsearch/v1&lt;/code&gt;, it stops working on &lt;strong&gt;January 1, 2027&lt;/strong&gt;. Google has already closed the API to new customers, and existing ones are being moved off it.&lt;/p&gt;

&lt;p&gt;I went through this myself and ended up building a small open-source tool for it. This post covers what's changing, what your options are, and the approach I went with, including where it falls short.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's actually shutting down
&lt;/h2&gt;

&lt;p&gt;The Custom Search JSON API is the one where you send &lt;code&gt;key&lt;/code&gt;, &lt;code&gt;cx&lt;/code&gt; and &lt;code&gt;q&lt;/code&gt;, and get back JSON with an &lt;code&gt;items&lt;/code&gt; array. For years it was the easy way to put web search into a script, an internal tool, or more recently an AI agent: 100 free queries a day, then $5 per 1,000.&lt;/p&gt;

&lt;p&gt;Google's suggested replacements aren't the same product:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Vertex AI Search&lt;/strong&gt; is built for searching your own content, or a limited set of sites (up to 50 domains). It isn't a general web search API.&lt;/li&gt;
&lt;li&gt;For full web search, Google points you to a &lt;strong&gt;partner-only&lt;/strong&gt; offering with no public pricing, where you have to contact them.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So if you were using it for general web search, there's no official drop-in path.&lt;/p&gt;

&lt;h2&gt;
  
  
  Your three options
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Rewrite for a new search API.&lt;/strong&gt; Brave, Serper, Exa, Tavily and others all have good APIs. But each one returns a different JSON shape, so you rewrite your parsing, pagination and error handling. That's fine for one small script, and painful if the call is spread across several services or buried inside a library you don't own.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Switch to Vertex AI Search.&lt;/strong&gt; Makes sense if you were only ever searching your own sites. It's a different API, though, so it's still a rewrite.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Keep the old API shape and swap what's behind it.&lt;/strong&gt; Put a thin layer in front of a new provider that speaks the old format exactly. Your code keeps calling the same endpoint shape, you change the base URL, and that's it.&lt;/p&gt;

&lt;p&gt;I went with option 3.&lt;/p&gt;

&lt;h2&gt;
  
  
  The parts of the old API people underestimate
&lt;/h2&gt;

&lt;p&gt;If you try to build a compatibility layer yourself, "return some JSON with &lt;code&gt;items&lt;/code&gt;" isn't enough. Code that has run against this API for years quietly depends on details like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;items&lt;/code&gt; is missing entirely when there are no results.&lt;/strong&gt; It isn't an empty list. Plenty of code does &lt;code&gt;if "items" in res&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;htmlSnippet&lt;/code&gt; and &lt;code&gt;htmlTitle&lt;/code&gt;&lt;/strong&gt; carry &lt;code&gt;&amp;lt;b&amp;gt;&lt;/code&gt; tags around matched words, and some UIs render them directly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;queries.nextPage&lt;/code&gt;&lt;/strong&gt; is how most pagination loops know when to stop.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The 100-result window.&lt;/strong&gt; &lt;code&gt;start&lt;/code&gt; + &lt;code&gt;num&lt;/code&gt; can't go past 100, and &lt;code&gt;num&lt;/code&gt; is 1 to 10. Google returns a specific 400 error if you exceed these.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The error format.&lt;/strong&gt; Retry logic often checks for &lt;code&gt;429&lt;/code&gt; with &lt;code&gt;RESOURCE_EXHAUSTED&lt;/code&gt;. A new provider returning &lt;code&gt;402 Payment Required&lt;/code&gt; hits a code path your client has never seen.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;totalResults&lt;/code&gt; is a string&lt;/strong&gt;, not a number.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Getting these right is most of the work.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I built: cse-compat
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/csecompat/cse-compat" rel="noopener noreferrer"&gt;cse-compat&lt;/a&gt; is a small Cloudflare Worker that serves &lt;code&gt;/customsearch/v1&lt;/code&gt; with the old contract: same parameters, same response fields, same error format. Behind it, it calls a search provider using &lt;strong&gt;your own API key&lt;/strong&gt; (Serper today, with a Brave adapter included). It's open source under Apache-2.0 and runs fine on Cloudflare's free plan.&lt;/p&gt;

&lt;p&gt;You can try the public demo without signing up:&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://csecompat.com/customsearch/v1?key=demo&amp;amp;cx=test&amp;amp;q=hello"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;(The demo has a daily cap, so for real use you deploy your own copy.)&lt;/p&gt;

&lt;h3&gt;
  
  
  Deploying your own
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/csecompat/cse-compat.git &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;cse-compat
npm &lt;span class="nb"&gt;install
&lt;/span&gt;npx wrangler secret put SERPER_API_KEY
npx wrangler secret put PROXY_KEYS
npx wrangler deploy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;SERPER_API_KEY&lt;/code&gt; is your key from serper.dev. &lt;code&gt;PROXY_KEYS&lt;/code&gt; is a key you make up, which your apps send as &lt;code&gt;?key=&lt;/code&gt; instead of the old Google key.&lt;/p&gt;

&lt;h2&gt;
  
  
  Changing your code
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Plain HTTP
&lt;/h3&gt;

&lt;p&gt;Only the host and the key change:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="gd"&gt;- https://www.googleapis.com/customsearch/v1?key=GOOGLE_KEY&amp;amp;cx=YOUR_CX&amp;amp;q=best+pizza
&lt;/span&gt;&lt;span class="gi"&gt;+ https://cse.yourdomain.workers.dev/customsearch/v1?key=YOUR_PROXY_KEY&amp;amp;cx=YOUR_CX&amp;amp;q=best+pizza
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Python (google-api-python-client)
&lt;/h3&gt;

&lt;p&gt;The official client has Google's host built in, but it lets you override it:&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;from&lt;/span&gt; &lt;span class="n"&gt;googleapiclient.discovery&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;build&lt;/span&gt;

&lt;span class="n"&gt;service&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;customsearch&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;v1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;developerKey&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;CSE_COMPAT_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;client_options&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;api_endpoint&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://cse.yourdomain.workers.dev&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;static_discovery&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Everything below is unchanged
&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cse&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;lectures&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cx&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;CX&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;num&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="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;res&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;items&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]):&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;title&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;link&lt;/span&gt;&lt;span class="sh"&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;static_discovery=True&lt;/code&gt; makes the client use its built-in API description instead of fetching it from Google, so nothing depends on Google's servers after the shutdown. There's a longer guide &lt;a href="https://csecompat.com/guides/google-api-python-client/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Node (googleapis)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;google&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;googleapis&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;customsearch&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;customsearch&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;v1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;rootUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://cse.yourdomain.workers.dev&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&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;res&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;customsearch&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;API_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;cx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CX&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;q&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;lectures&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="c1"&gt;// res.data.items works as before&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Go's client has &lt;code&gt;option.WithEndpoint(...)&lt;/code&gt; for the same thing, and Java has &lt;code&gt;setRootUrl&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's different (being honest)
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Google CSE&lt;/th&gt;
&lt;th&gt;cse-compat&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Request and response format&lt;/td&gt;
&lt;td&gt;original&lt;/td&gt;
&lt;td&gt;same&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Error format&lt;/td&gt;
&lt;td&gt;original&lt;/td&gt;
&lt;td&gt;same&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Ranking&lt;/td&gt;
&lt;td&gt;Google's index&lt;/td&gt;
&lt;td&gt;your provider's&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Image search (&lt;code&gt;searchType=image&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;not yet (returns a clear error)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;pagemap&lt;/code&gt; data&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;not yet&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CSE console features (promotions, refinements)&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;td&gt;no&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A few more things worth knowing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Results come from your provider.&lt;/strong&gt; Serper is a SERP API, so its results are Google-derived. Brave has its own independent index. Same shape either way, but the results won't match Google exactly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Serper's free plan rejects some exact-match queries&lt;/strong&gt; that include a quoted domain. cse-compat returns those as a normal 400 error instead of crashing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;totalResults&lt;/code&gt; is a lower bound&lt;/strong&gt;, not a made-up big number, so pagination loops don't run forever.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  One thing to do before January 1, whatever you choose
&lt;/h2&gt;

&lt;p&gt;If you still have a working CSE key, &lt;strong&gt;save some real responses now&lt;/strong&gt;. After January 1 nobody can generate new ones, and they're the best way to check that any replacement, mine or not, behaves like the original.&lt;/p&gt;

&lt;p&gt;The repo has a script that captures a set of real responses and removes your key from them:&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="nv"&gt;GOOGLE_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;... &lt;span class="nv"&gt;GOOGLE_CX&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;... node scripts/capture-fixtures.mjs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A second script then compares any deployment against those captures, checking field by field that the shape matches:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node scripts/diff-golden.mjs https://cse.yourdomain.workers.dev YOUR_PROXY_KEY
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you'd like to contribute captured fixtures to the repo, I'd really appreciate it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;If your code is one small script, rewriting it for a new provider is probably simplest. If the Custom Search call is spread across services, or inside libraries you don't control, keeping the API shape and swapping what's behind it can save a lot of work.&lt;/p&gt;

&lt;p&gt;The repo is &lt;a href="https://github.com/csecompat/cse-compat" rel="noopener noreferrer"&gt;github.com/csecompat/cse-compat&lt;/a&gt;. Issues and PRs are welcome, and I'd love to hear what you use the Custom Search API for. I'm also considering a hosted version for teams that don't want to run their own worker. There's a waitlist on &lt;a href="https://csecompat.com" rel="noopener noreferrer"&gt;csecompat.com&lt;/a&gt; if that's you.&lt;/p&gt;

</description>
      <category>google</category>
      <category>api</category>
      <category>cloudflare</category>
      <category>python</category>
    </item>
  </channel>
</rss>
