<?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: Eugeniya Ivanova</title>
    <description>The latest articles on DEV Community by Eugeniya Ivanova (@eugeniya_ivanova_4a58eadc).</description>
    <link>https://dev.to/eugeniya_ivanova_4a58eadc</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%2F4017574%2F67a20934-fb30-41ef-bfcc-79a85f515c48.jpg</url>
      <title>DEV Community: Eugeniya Ivanova</title>
      <link>https://dev.to/eugeniya_ivanova_4a58eadc</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/eugeniya_ivanova_4a58eadc"/>
    <language>en</language>
    <item>
      <title>My plugin passed every check. It still didn't work.</title>
      <dc:creator>Eugeniya Ivanova</dc:creator>
      <pubDate>Tue, 13 Oct 2026 08:49:13 +0000</pubDate>
      <link>https://dev.to/eugeniya_ivanova_4a58eadc/my-plugin-passed-every-check-it-still-didnt-work-1d6n</link>
      <guid>https://dev.to/eugeniya_ivanova_4a58eadc/my-plugin-passed-every-check-it-still-didnt-work-1d6n</guid>
      <description>&lt;p&gt;We built a WordPress plugin. The idea was simple: you publish a post on your blog, and a couple of minutes later an announcement goes out to your social networks automatically. Headline, image, link. About two thousand lines of code. A small project, I thought.&lt;/p&gt;

&lt;p&gt;I should probably mention that I don't write the code myself. Claude does. My contribution is asking questions, testing things and occasionally refusing to accept an answer because something about it bothers me. This turned out to be one of those occasions.&lt;/p&gt;

&lt;p&gt;Before submitting, we went through all the checks. &lt;code&gt;phpcs&lt;/code&gt; against the &lt;a href="https://github.com/WordPress/WordPress-Coding-Standards" rel="noopener noreferrer"&gt;WordPress coding standards&lt;/a&gt;: zero errors, zero warnings. The official &lt;a href="https://wordpress.org/plugins/plugin-check/" rel="noopener noreferrer"&gt;Plugin Check&lt;/a&gt;: clean. WordPress directory review: approved. The plugin was published, and I was quite pleased with us.&lt;/p&gt;

&lt;p&gt;Then I installed it on an actual WordPress site. It didn't work.&lt;/p&gt;

&lt;h3&gt;
  
  
  The green checkmarks were checking the wrong thing
&lt;/h3&gt;

&lt;p&gt;Here's the embarrassing part: before submitting, we'd never actually run the plugin in WordPress. We'd tried, but our local environment wouldn't start. WP-CLI kept crashing with the latest PHP version, and instead of sorting that out, we carried on with static checks.&lt;/p&gt;

&lt;p&gt;The WordPress directory review checks whether a plugin follows the rules and meets security requirements. It doesn't mean somebody has tested whether your particular publishing workflow works. I understood this perfectly well in theory. In practice, I saw all those green checkmarks and decided we were finished.&lt;/p&gt;

&lt;p&gt;Eventually, we got the plugin running in &lt;a href="https://wordpress.org/playground/" rel="noopener noreferrer"&gt;WordPress Playground&lt;/a&gt;, which runs WordPress through WebAssembly without needing a traditional local server. It took one command to start. On the first proper test, we found two bugs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bug one: the Connect button led nowhere
&lt;/h3&gt;

&lt;p&gt;You click Connect, expecting to reach the Publora sign-in page, and WordPress sends you straight back to the admin dashboard. No error, no warning. You're just back where you started.&lt;/p&gt;

&lt;p&gt;The problem was &lt;a href="https://developer.wordpress.org/reference/functions/wp_safe_redirect/" rel="noopener noreferrer"&gt;&lt;code&gt;wp_safe_redirect()&lt;/code&gt;&lt;/a&gt;. We'd used it because, well, it has &lt;em&gt;safe&lt;/em&gt; in the name. Unfortunately, it also restricts redirects to approved hosts. Our sign-in page lives on another domain, so WordPress replaced the destination with the admin URL.&lt;/p&gt;

&lt;p&gt;The function was doing exactly what it was supposed to do. We were the ones asking it to do the wrong thing.&lt;/p&gt;

&lt;p&gt;The fix was to allow the Publora authentication host for that particular redirect:&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="nf"&gt;add_filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'allowed_redirect_hosts'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;$this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'allow_auth_host'&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;span class="nf"&gt;wp_safe_redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;302&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'Publora'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing here looked suspicious to the static analyzer. The code was valid, the redirect was safe, and the Connect button was completely useless.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bug two: the editor kept losing the image
&lt;/h3&gt;

&lt;p&gt;The second problem was harder to spot. When publishing from Gutenberg, the social announcement sometimes went out without the featured image. It also ignored the post settings: whether to share the post at all, which networks to use, and so on.&lt;/p&gt;

&lt;p&gt;We'd attached publishing to the &lt;code&gt;transition_post_status&lt;/code&gt; hook. When a post changes to &lt;code&gt;publish&lt;/code&gt;, send the announcement. That seemed reasonable.&lt;/p&gt;

&lt;p&gt;Except Gutenberg doesn't necessarily save everything in one request. The post status can change before the featured image and some settings have finished saving. The old metabox can send its data separately too. Our hook fired, we read whatever was available at that moment, and sent it off to social. Which, often enough, was nothing.&lt;/p&gt;

&lt;p&gt;The plugin wasn't losing the image. We were asking for it before it had arrived.&lt;/p&gt;

&lt;p&gt;We moved the publishing logic to &lt;a href="https://developer.wordpress.org/reference/hooks/wp_after_insert_post/" rel="noopener noreferrer"&gt;&lt;code&gt;wp_after_insert_post&lt;/code&gt;&lt;/a&gt;, where we could work with the saved post data, and added a check to avoid sending an announcement when an already-published post was updated:&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="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$post_before&lt;/span&gt; &lt;span class="k"&gt;instanceof&lt;/span&gt; &lt;span class="n"&gt;WP_Post&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="s1"&gt;'publish'&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nv"&gt;$post_before&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;post_status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Neither bug had anything to do with syntax or coding standards. We needed to see how WordPress actually behaved: where it redirected users, when Gutenberg saved its data, and which hooks fired along the way.&lt;/p&gt;

&lt;h3&gt;
  
  
  And now, how we dug them up in the first place
&lt;/h3&gt;

&lt;p&gt;This is probably my favourite part of the story, although I wasn't enjoying it quite so much at the time.&lt;/p&gt;

&lt;p&gt;When we discovered the plugin wasn't working, Claude suggested contacting WordPress directory support. They know the platform, they could help us investigate, and we'd already passed review. A reasonable suggestion.&lt;/p&gt;

&lt;p&gt;I didn't want to do that yet.&lt;/p&gt;

&lt;p&gt;Partly because I hate waiting for support replies, and partly because we hadn't even managed to run the thing properly ourselves. It seemed a little early to ask somebody else why it was broken.&lt;/p&gt;

&lt;p&gt;So I asked Claude to go back through the documentation and help me set up a working test environment. We opened Playground and started trying things. Publish with an image. Publish without one. Disable sharing. Select Mastodon only. Try the classic editor. Watch what happens, check the logs, change something and try again.&lt;/p&gt;

&lt;p&gt;By the end of the evening, we'd found both bugs and fixed them. No support tickets, no waiting for somebody to investigate. Just us, WordPress and a rather overdue first proper test.&lt;/p&gt;

&lt;p&gt;I still can't read PHP nearly as well as Claude. When it shows me a function, I often need an explanation of what I'm looking at. But I can tell when we're trying to get around a problem instead of understanding it. And sometimes I can be sufficiently stubborn about that to make us investigate.&lt;/p&gt;

&lt;p&gt;This was one of those times. Claude did the technical work, and I contributed a firm refusal to send an email. An unexpectedly useful division of labour.&lt;/p&gt;

&lt;h3&gt;
  
  
  What I took from it
&lt;/h3&gt;

&lt;p&gt;I knew static checks weren't the same as testing a working plugin. I really did. I just apparently needed to publish one before remembering.&lt;/p&gt;

&lt;p&gt;The other thing that stayed with me was how close we came to asking support for help with two bugs we hadn't even tried to reproduce properly. There are plenty of problems where support is exactly what you need. This wasn't one of them.&lt;/p&gt;

&lt;p&gt;And I'm glad I pushed back on Claude's suggestion. Not because Claude was wrong to offer it, but because I would have learned very little from sending that email and waiting for somebody else to find the problem.&lt;/p&gt;

&lt;p&gt;If you're building a WordPress plugin, I'd recommend running it in Playground before submitting it. We spent longer getting our broken local environment to cooperate than it took to find both bugs once WordPress was actually running.&lt;/p&gt;

&lt;p&gt;The plugin passed review. The Connect button still sent me back to the dashboard. I think I'll remember that particular combination for a while.&lt;/p&gt;




&lt;p&gt;Anyone else had something pass every check and then fail the first time you actually used it?&lt;/p&gt;

</description>
      <category>wordpress</category>
      <category>php</category>
      <category>webdev</category>
      <category>showdev</category>
    </item>
    <item>
      <title>A like is face-to-face. A follow is standing side by side. At least that's how it feels to me.</title>
      <dc:creator>Eugeniya Ivanova</dc:creator>
      <pubDate>Wed, 07 Oct 2026 10:23:14 +0000</pubDate>
      <link>https://dev.to/eugeniya_ivanova_4a58eadc/a-like-is-face-to-face-a-follow-is-standing-side-by-side-at-least-thats-how-it-feels-to-me-ob0</link>
      <guid>https://dev.to/eugeniya_ivanova_4a58eadc/a-like-is-face-to-face-a-follow-is-standing-side-by-side-at-least-thats-how-it-feels-to-me-ob0</guid>
      <description>&lt;p&gt;Fair warning: I'm not a physicist, sociologist or scientist of any kind. I found a couple of studies, they've been sitting in my head for a week, and apparently the only way to get them out is to write about them. So this is less "here's how it works" and more "look what I found."&lt;/p&gt;

&lt;p&gt;It started with kids. Or, more precisely, with scientists putting radio tags on preschoolers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Kids on a playground behave like a state of matter
&lt;/h3&gt;

&lt;p&gt;A team at the University of Miami put high-precision radio tags on preschoolers and watched how they moved. Not where they ran or what they played, but how they arranged themselves relative to each other. This did not sound especially exciting to me at first.&lt;/p&gt;

&lt;p&gt;Then I read what they found.&lt;/p&gt;

&lt;p&gt;In the &lt;a href="https://pmc.ncbi.nlm.nih.gov/articles/PMC11681628/" rel="noopener noreferrer"&gt;2024 study&lt;/a&gt;, when the kids had plenty of room, they moved a bit like a &lt;strong&gt;gas&lt;/strong&gt;: moving around, bumping into each other, separating again. Put them in a tighter space and they started forming small, more stable groups, closer to &lt;strong&gt;droplets of liquid&lt;/strong&gt;. Some stayed together, others moved in and out. The researchers describe it as a phase transition. Except the particles are preschoolers and the container is a daycare room.&lt;/p&gt;

&lt;p&gt;This isn't entirely new either. Physicists have been using similar models for crowds and pedestrians for decades. Back &lt;a href="https://content.wolfram.com/sites/13/2018/02/06-5-1.pdf" rel="noopener noreferrer"&gt;in 1992&lt;/a&gt;, pedestrian movement was already being described with a "gas-kinetic Boltzmann model." There is also a whole field called &lt;strong&gt;active matter&lt;/strong&gt;, which studies things that use their own energy to move: bacteria, fish, birds and, depending on the paper you're reading, apparently us.&lt;/p&gt;

&lt;p&gt;This was already enough to send me down a hole. Then I found the second paper.&lt;/p&gt;

&lt;h3&gt;
  
  
  Then they went and measured the distance
&lt;/h3&gt;

&lt;p&gt;In a &lt;a href="https://www.science.org/doi/10.1126/sciadv.adz6890" rel="noopener noreferrer"&gt;2026 paper&lt;/a&gt;, the same team looked at orientation: not just where the children were, but which way they were facing.&lt;/p&gt;

&lt;p&gt;At distances below roughly sixty-five centimeters, the children tended to stand &lt;strong&gt;side by side&lt;/strong&gt;, facing in the same direction. Farther apart, they were more likely to &lt;strong&gt;face each other&lt;/strong&gt;. The change was surprisingly sharp rather than gradual. The authors describe it as a phase transition, although the reviewers are a little more cautious about how much the data can support that language.&lt;/p&gt;

&lt;p&gt;But the part that stayed with me was much simpler: someone had actually measured the distance.&lt;/p&gt;

&lt;p&gt;There is a number, in centimeters, where the way these kids relate to each other changes. And I spend most of my day in a place where distance supposedly doesn't exist at all.&lt;/p&gt;

&lt;h3&gt;
  
  
  Except each of us has our own room
&lt;/h3&gt;

&lt;p&gt;This is where the comparison starts becoming less convenient.&lt;/p&gt;

&lt;p&gt;The kids have one physical room. Its walls are in the same place for everyone. A feed doesn't work like that. The algorithm builds a different version for each person. My Instagram and yours have the same logo at the top, but they're not really the same room.&lt;/p&gt;

&lt;p&gt;We're all supposedly inside the same network while seeing different people, in a different order, at different frequencies. Even the size of the room changes depending on what the algorithm decides to put near you that day.&lt;/p&gt;

&lt;p&gt;So the gas-in-a-flask comparison only gets me so far. The flask, at least, has the decency to stay put.&lt;/p&gt;

&lt;h3&gt;
  
  
  And with AI I've stopped understanding what I'm even made of
&lt;/h3&gt;

&lt;p&gt;This is where I wandered quite a long way from the paper.&lt;/p&gt;

&lt;p&gt;The physics works with a fairly useful assumption: one particle has one physical location. A molecule doesn't leave a second version of itself at the other end of the room while the original goes for lunch.&lt;/p&gt;

&lt;p&gt;My online life has become less tidy.&lt;/p&gt;

&lt;p&gt;A couple of years ago I didn't trust AI much at all. The mistakes were easy to see: six fingers, broken text, confidently ridiculous answers. In retrospect, there was something comfortable about that. Then it got better, I started using it for more things, and somewhere along the way it became my pocket techie. I write with it, build with it, research with it and publish through it.&lt;/p&gt;

&lt;p&gt;So while reading about these children as particles, I started wondering what exactly counts as my particle now. Just me? Me plus an agent? It can publish something I prepared while I'm asleep. It can work across several services while I'm physically sitting in one chair doing something else. My body still has one location. My activity doesn't.&lt;/p&gt;

&lt;p&gt;This is not, to be clear, my attempt to introduce a new state of matter. I have enough problems getting software through review without taking on physics.&lt;/p&gt;

&lt;p&gt;But it did make something about feeds feel less mysterious to me. They can feel much more crowded without there necessarily being more people. One person can simply produce and appear in more places than they could before. Agents make that easier again.&lt;/p&gt;

&lt;h3&gt;
  
  
  So what distance is a follow, then?
&lt;/h3&gt;

&lt;p&gt;Which brought me back to the sixty-five centimeters. If physical distance changes how the children orient toward each other, I started wondering what the equivalent distance might be online.&lt;/p&gt;

&lt;p&gt;I have my own completely unmeasured version.&lt;/p&gt;

&lt;p&gt;A like, a reaction or a quick comment feels &lt;strong&gt;face-to-face&lt;/strong&gt; to me. I noticed you. For a moment my attention is pointed directly at you and whatever you just said.&lt;/p&gt;

&lt;p&gt;A follow feels more like &lt;strong&gt;standing side by side&lt;/strong&gt;. I'm not only looking at this one thing you did. I've decided I want to keep seeing what you're looking at next.&lt;/p&gt;

&lt;p&gt;What amused me is that the order isn't completely unlike the playground observation. At a greater distance, the children are more often facing each other. Closer together, they're more often oriented in the same direction. My brain apparently decided that clicking Follow belongs somewhere in that second category.&lt;/p&gt;

&lt;p&gt;There is absolutely no sixty-five-centimeter result for LinkedIn hiding in these papers. That's my part.&lt;/p&gt;

&lt;h3&gt;
  
  
  Although I could, of course, be making all of this up
&lt;/h3&gt;

&lt;p&gt;And I probably am, at least partly. I haven't measured any of this. Maybe a like feels like standing beside someone to you: a small acknowledgment from somebody already on your side. Maybe following someone is more like sitting across from them because you want to watch what they do. The geometry could be completely different.&lt;/p&gt;

&lt;p&gt;But now I really want somebody to measure it.&lt;/p&gt;

&lt;p&gt;If children in a room have a distance where their orientation toward each other changes, I wonder whether online relationships have anything similar. Maybe it's a follow. Maybe the first DM. Maybe the first time you reply to somebody instead of just liking what they wrote.&lt;/p&gt;




&lt;p&gt;I wrote this together with Claude: the philosophizing is mine, the structure and formatting are Claude's.&lt;/p&gt;

&lt;p&gt;I don't know. But I'm curious how you'd arrange these if they were distances: a like, a comment, a repost, a follow, a DM. Which ones feel face-to-face to you, and which feel more like standing beside someone?&lt;/p&gt;

</description>
      <category>science</category>
      <category>socialmedia</category>
      <category>ai</category>
      <category>discuss</category>
    </item>
    <item>
      <title>Notion's API almost never errors out. That's the hard part.</title>
      <dc:creator>Eugeniya Ivanova</dc:creator>
      <pubDate>Tue, 06 Oct 2026 09:46:06 +0000</pubDate>
      <link>https://dev.to/eugeniya_ivanova_4a58eadc/notions-api-almost-never-errors-out-thats-the-hard-part-5g2k</link>
      <guid>https://dev.to/eugeniya_ivanova_4a58eadc/notions-api-almost-never-errors-out-thats-the-hard-part-5g2k</guid>
      <description>&lt;p&gt;We were getting &lt;a href="https://www.notion.com/connections/publora" rel="noopener noreferrer"&gt;Publora into Notion&lt;/a&gt;: an integration that reads rows from your database and turns them into scheduled posts. Built it, tested it, submitted it. Somewhere along the way I noticed that most of my problems with the API didn't produce errors. The request would succeed, and then I'd discover that what happened wasn't quite what I thought I'd asked for.&lt;/p&gt;

&lt;p&gt;Here are the ones that cost me time. If you're building on Notion, they might save you some.&lt;/p&gt;

&lt;h3&gt;
  
  
  Scopes that are easy to miss
&lt;/h3&gt;

&lt;p&gt;On Notion's consent screen, you pick pages: share these, leave those alone. What isn't nearly as obvious is what the integration is actually allowed to &lt;em&gt;do&lt;/em&gt; with them. Ours had extra permissions enabled by default: inserting content and reading users along with their emails.&lt;/p&gt;

&lt;p&gt;I only noticed because I tried the things the integration wasn't supposed to be able to do with a live token:&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="err"&gt;GET /v1/users    → 200, users with emails
POST /v1/pages   → 200, page created
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I trimmed the scopes down to Read + Update content and tried again:&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="err"&gt;GET /v1/users    → 403 restricted_resource
POST /v1/pages   → 403 restricted_resource
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's all we need. Publora has to read the row and write a status back to it; it doesn't need permission to create pages. The consent screen is much better at showing which pages you've shared than explaining exactly what the token can do with them, so I ended up checking the permissions with actual requests.&lt;/p&gt;

&lt;p&gt;Related catch: changing the permissions doesn't change an existing token. I unticked the boxes in the dashboard, tested again and still had the old permissions. You need to disconnect completely and go through OAuth again.&lt;/p&gt;

&lt;h3&gt;
  
  
  Places where the API says "ok" and does nothing
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;It can't publish a page, and won't admit it.&lt;/strong&gt; &lt;code&gt;public_url&lt;/code&gt; is read-only. &lt;code&gt;PATCH&lt;/code&gt; it and the request goes through without an error, but nothing changes. I spent twenty minutes changing the request body before discovering there was nothing wrong with my request. &lt;code&gt;Share → Publish&lt;/code&gt; is a manual action; the API simply doesn't expose it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It hands you &lt;code&gt;icon: null&lt;/code&gt;, then refuses it back.&lt;/strong&gt; Read a block and you can get &lt;code&gt;icon: null&lt;/code&gt;. Send the same object back in a write and it fails. We ended up stripping empty fields before writes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Views can't be created over the API.&lt;/strong&gt; If you want a Calendar or Board view, you still have to switch the layout manually.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You can't move a block.&lt;/strong&gt; There's no equivalent of "move this above that." You insert a copy with &lt;code&gt;after: &amp;lt;block_id&amp;gt;&lt;/code&gt; and archive the old block. Not exactly moving, but the result looks moved, which apparently is enough.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Skill flag can't be set over REST.&lt;/strong&gt; The toggle exists in the UI. It exists in Notion's MCP tools. Plain REST doesn't have it.&lt;/p&gt;

&lt;h3&gt;
  
  
  The one everyone's tripping on right now
&lt;/h3&gt;

&lt;p&gt;As of &lt;a href="https://developers.notion.com/guides/get-started/upgrade-guide-2025-09-03" rel="noopener noreferrer"&gt;API version 2025-09-03&lt;/a&gt;, rows don't live directly under the database anymore. They belong to a data source. A database can now have several data sources, which means the old &lt;code&gt;POST /v1/databases/{id}/query&lt;/code&gt; can fail with an error about multiple data sources not being supported.&lt;/p&gt;

&lt;p&gt;The new path is: fetch the database, take &lt;code&gt;data_sources[0].id&lt;/code&gt;, then call &lt;code&gt;POST /v1/data_sources/{id}/query&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This one confused me because everything still looks like a database in Notion. From the API's point of view, though, there's now another thing in the middle.&lt;/p&gt;

&lt;h3&gt;
  
  
  A couple of process things
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Access is granted page by page, and a token isn't enough.&lt;/strong&gt; You have to connect each page through ••• → Connections. Otherwise &lt;code&gt;/v1/search&lt;/code&gt; can quite correctly return zero results. I spent some time suspecting search before realizing we'd simply never given the integration the page.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ten minutes for an OAuth round is too short.&lt;/strong&gt; Our session key originally lived for 600 seconds. That's plenty if you know exactly which database you're looking for. It's less generous when you're scrolling through a long list trying to remember what you called it six months ago. We changed it to thirty minutes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bots get a captcha.&lt;/strong&gt; I tried to take screenshots of our own public page automatically. Playwright got "Verify you are human" twice. I took the screenshots myself. Very advanced automation.&lt;/p&gt;

&lt;p&gt;One nice surprise along the way: we map columns by synonyms. &lt;code&gt;caption&lt;/code&gt;, &lt;code&gt;text&lt;/code&gt; and &lt;code&gt;message&lt;/code&gt; can all be the post text; &lt;code&gt;platform&lt;/code&gt; and &lt;code&gt;networks&lt;/code&gt; can identify the channels. While testing, I gave it a database with a typo in one of the column names. It matched anyway. So people don't have to rebuild their content calendar around our template, which was one thing I really didn't want to ask them to do.&lt;/p&gt;

&lt;h3&gt;
  
  
  What I'd take from it
&lt;/h3&gt;

&lt;p&gt;After working with the Notion API for a while, I stopped treating a successful response as proof that I was finished. I check the actual result now: what permissions the token has, whether the write really changed the thing I meant to change, whether the integration can actually see the page.&lt;/p&gt;

&lt;p&gt;This sounds obvious written down. It was apparently less obvious to me while I was spending twenty minutes trying to PATCH a field that couldn't be changed.&lt;/p&gt;




&lt;p&gt;I wrote this with help from Claude.&lt;/p&gt;

&lt;p&gt;Have you worked with an API where the request succeeds and you only find out later that it didn't quite do what you thought?&lt;/p&gt;

</description>
      <category>notion</category>
      <category>api</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Figma let us in with one email. Canva took forty-seven days.</title>
      <dc:creator>Eugeniya Ivanova</dc:creator>
      <pubDate>Wed, 30 Sep 2026 06:47:05 +0000</pubDate>
      <link>https://dev.to/eugeniya_ivanova_4a58eadc/figma-let-us-in-with-one-email-canva-took-forty-seven-days-345g</link>
      <guid>https://dev.to/eugeniya_ivanova_4a58eadc/figma-let-us-in-with-one-email-canva-took-forty-seven-days-345g</guid>
      <description>&lt;p&gt;A couple of weeks ago I wrote about &lt;a href="https://dev.to/eugeniya_ivanova_4a58eadc/i-was-sure-figma-would-reject-my-cover-art-the-email-asked-about-something-else-2j4k"&gt;getting our plugin into Figma&lt;/a&gt;. They sent one email asking for a video, we sent it, and that was basically it. I remember being surprised by how calmly the whole thing went.&lt;/p&gt;

&lt;p&gt;Canva was running in parallel. Canva was not calm.&lt;/p&gt;

&lt;p&gt;Forty-seven days from submission to approval. Four rounds of review. Five versions of the app. This wasn't because we'd managed to do everything wrong. Canva has three review stages that happen in order, and design review alone can take up to two weeks and involve several people. One reviewer even warned me that most apps get notes and that getting them doesn't mean you're failing review. This was useful information somewhere around round two. By round four, less so.&lt;/p&gt;

&lt;p&gt;A lot of what slowed us down wasn't code. It was figuring out what a review comment was actually asking us to fix. If you're submitting an app there, these are the things I wish I'd known before we started.&lt;/p&gt;

&lt;h3&gt;
  
  
  The requirement is called one thing, and the real issue is another
&lt;/h3&gt;

&lt;p&gt;The title of a Canva review note isn't always a particularly good description of the problem. We learned to stop treating it as one.&lt;/p&gt;

&lt;p&gt;The clearest example came in the third round. We got three different checklist items back. Under all three, the reviewer had attached essentially the same evidence: "even if you pick 'Add video,' the exported file is still a PNG." I saw three items and started looking for three problems. There was one.&lt;/p&gt;

&lt;p&gt;The bug itself was in the format config, not Publora. I checked that first because blaming our own API is a habit I've developed. We had one format with a single slot for "image or video." Canva goes through the formats in order, that combined slot doesn't match a video page correctly, the fallback kicks in, and the video comes out as a freeze frame. We split it into two formats. To make sure we'd understood what was happening, we put video first and got the opposite bug: static pages started rendering as mp4. At least that was convincing.&lt;/p&gt;

&lt;p&gt;Another review item was called "Buttons not full width." I went looking for buttons. In the attached screenshot, the problem was actually a narrow dropdown that said "In an hour." After that I started opening the screenshots before doing much with the title of the ticket. It saved time.&lt;/p&gt;

&lt;p&gt;Then there was one where the documentation and the reviewer simply disagreed. The docs said the object panel on the featured image should be dark grey. The reviewer wanted it white. We made it white. At that point I was no longer interested in winning an argument with a PDF.&lt;/p&gt;

&lt;h3&gt;
  
  
  We did it not the way they asked, and it came out more honest
&lt;/h3&gt;

&lt;p&gt;One request was harder because we couldn't implement it literally. Canva wanted an account block that was always visible, with the user's name and avatar.&lt;/p&gt;

&lt;p&gt;Publora doesn't really have that concept in this part of the product. What matters is which social channels you're connected to and where the post is going. I still tried every endpoint name I could think of: &lt;code&gt;me&lt;/code&gt;, &lt;code&gt;user&lt;/code&gt;, &lt;code&gt;account&lt;/code&gt;, &lt;code&gt;profile&lt;/code&gt;. Four different ways to get a 404.&lt;/p&gt;

&lt;p&gt;So we built the block from the information we actually have. We fetch the connected channels and show their names and platforms in the header. Instead of a generic user profile, you see the actual accounts you're about to publish to: this X account, this LinkedIn, this Instagram.&lt;/p&gt;

&lt;p&gt;That ended up making more sense for Publora anyway. The reviewer wanted the user to have account context while publishing. We could provide that context; it just wasn't the name-and-avatar version described in the ticket.&lt;/p&gt;

&lt;h3&gt;
  
  
  A piece of technical luck I didn't earn
&lt;/h3&gt;

&lt;p&gt;There was also one part I expected to be difficult that turned out not to be difficult at all.&lt;/p&gt;

&lt;p&gt;We don't have to upload the media from Canva ourselves. Canva exports the design and gives us a link, then Publora downloads the file from that link. The separate upload flow I'd been expecting to build simply wasn't necessary.&lt;/p&gt;

&lt;p&gt;Our OAuth was an even better surprise. At some point we discovered that we'd already rolled out what we needed and apparently forgotten about it. Our regular REST API accepted the token, so sign-in worked without any new code. I would like more integrations to be solved by discovering that we already did the work six months ago.&lt;/p&gt;

&lt;p&gt;The Cloudflare shim ended up at about a hundred lines and stores nothing. It's there for CORS: our API only accepts browser requests from our own domains, while Canva naturally runs on Canva's.&lt;/p&gt;

&lt;h3&gt;
  
  
  A few things from off-screen, so you don't step on them
&lt;/h3&gt;

&lt;p&gt;Design review is separate and, for us, was the longest stage. Our fourth round came back with six comments, all about the listing images. Nothing was wrong with the app itself. So if you're planning around the technical work only, leave some time for somebody to have opinions about screenshots.&lt;/p&gt;

&lt;p&gt;You also can't keep changing things while the review is running. When they say, "Please hold off on making any changes while it's under review," they mean it. Fixes go into a new version, and not everything necessarily carries over, so we ended up checking each version again from the beginning.&lt;/p&gt;

&lt;p&gt;A demo video is mandatory, although we didn't find it in the main checklist. The requirement appears next to the field itself: without a video link, you can't submit. We recorded three versions because the app changed after review rounds and the previous recording stopped matching it.&lt;/p&gt;

&lt;p&gt;And then we nearly published TEST to a real LinkedIn account. During one recording, the post went quite happily into the real schedule for the next morning because the publishing control still said "Tomorrow, 9:00" instead of "Save as draft." Technically, everything worked. Unfortunately, that was the problem. We noticed it while checking the recording and deleted the post before morning. I've already written &lt;a href="https://dev.to/eugeniya_ivanova_4a58eadc/the-agent-posted-successfully-to-the-wrong-account-3kf3"&gt;an entire article about checking publishing state&lt;/a&gt;, so naturally I had to demonstrate the problem again myself.&lt;/p&gt;

&lt;h3&gt;
  
  
  How it ended
&lt;/h3&gt;

&lt;p&gt;On September 21, the status changed to Application Approved. After that, the listing text was translated into eighteen languages and we got an email saying the app was ready for release. I then waited for the release button to become active before realizing that nothing was going to happen until I actually pressed it myself. Forty-seven days had apparently trained me to wait for permission.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.canva.com/apps/AAHOGD2rfQA/publora" rel="noopener noreferrer"&gt;Publora is in Canva now&lt;/a&gt;. Publer, Metricool and a few other social tools have been there for a while. Our flow is a little different: instead of exporting the design to a media library and finishing the post in another dashboard, we use Canva's Content Publisher intent. You can take the design you're already working on and publish it to one of your connected networks without leaving the Canva editor.&lt;/p&gt;

&lt;p&gt;Canva says &lt;a href="https://www.canva.com/newsroom/news/canva-2025-wrap/" rel="noopener noreferrer"&gt;more than 250 million people&lt;/a&gt; use the product each month. Some of them can now finish the job without downloading a picture, opening another tab and uploading the same picture again.&lt;/p&gt;

&lt;p&gt;After five versions, I'll take that.&lt;/p&gt;




&lt;p&gt;I wrote this one with Claude as well. I told it what happened over those forty-seven days, it drafted, and I cut anything that hadn't happened that way.&lt;/p&gt;

&lt;p&gt;Have you ever spent a review round fixing the thing the ticket was named after, only to find the real problem was in the screenshot?&lt;/p&gt;

</description>
      <category>canva</category>
      <category>api</category>
      <category>webdev</category>
      <category>showdev</category>
    </item>
    <item>
      <title>The portal said "Live." For a few hours the page kept saying the connector didn't exist.</title>
      <dc:creator>Eugeniya Ivanova</dc:creator>
      <pubDate>Thu, 24 Sep 2026 07:49:57 +0000</pubDate>
      <link>https://dev.to/eugeniya_ivanova_4a58eadc/the-portal-said-live-for-a-few-hours-the-page-kept-saying-the-connector-didnt-exist-m11</link>
      <guid>https://dev.to/eugeniya_ivanova_4a58eadc/the-portal-said-live-for-a-few-hours-the-page-kept-saying-the-connector-didnt-exist-m11</guid>
      <description>&lt;p&gt;I've submitted to just about every directory I could reach by now. Chrome, Firefox, Edge, Zapier, IFTTT, Figma, ChatGPT. Every time it's someone else's place: you show up, they check what you've brought, sometimes they send you back to fix it, sometimes they let you in. I've written enough about &lt;a href="https://dev.to/eugeniya_ivanova_4a58eadc/chrome-rejected-us-twice-firefox-approved-instantly-edge-is-still-thinking-e0e"&gt;review rounds and strange submission rules&lt;/a&gt; by now.&lt;/p&gt;

&lt;p&gt;And this week we submitted Publora to the official &lt;a href="https://claude.ai/directory/publora" rel="noopener noreferrer"&gt;Claude connectors directory&lt;/a&gt;. For some reason I'd been preparing for this one longer than any of the others. Not technically. The work was done. Apparently I had some feelings left over.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fifteen minutes I'd been walking toward the longest
&lt;/h3&gt;

&lt;p&gt;The submission itself took about fifteen minutes. A form, a button, done. Submitted yesterday, in the directory today. Behind those fifteen minutes was a month and a half of work by the whole team: thirteen pages of Anthropic documentation, checking the server against fifteen requirements with live requests, and manually running all eighteen tools against production.&lt;/p&gt;

&lt;p&gt;So why did the easiest submission bother me more than some of the genuinely horrible ones?&lt;/p&gt;

&lt;p&gt;I think it's because all the previous directories felt like other people's territory. Claude doesn't. I'm not an engineer; my job is getting people to use our product, and almost everything I've built this year I've built with Claude somewhere next to me. It's my pocket techie: it writes the code, I lead, argue and make it redo things. Twenty articles, a dozen and a half integrations, all those submissions. And now Publora is sitting in Claude's own directory. That felt different.&lt;/p&gt;

&lt;p&gt;That's enough sentiment. There were also several stupid things that cost us time, and those are probably more useful if you're submitting a connector yourself.&lt;/p&gt;

&lt;h3&gt;
  
  
  The portal says one thing, reality says another
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The main thing I'd keep in mind: a green status in the portal doesn't necessarily mean the thing next to it is already working.&lt;/strong&gt; We found that out in three different places.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Live in the directory," and the page is empty.&lt;/strong&gt; We hit publish and the portal immediately reported "live." The listing page, meanwhile, spent several hours returning "This connector doesn't exist." The View listing button went to the same page, and search found only our temporary connection. We'd already written a support email and, luckily, hadn't sent it yet. A few hours later the listing appeared on its own. If you see the same thing, give it some time before writing to support.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The portal decides the server is authless when it isn't.&lt;/strong&gt; At the authorization step it checks &lt;code&gt;tools/list&lt;/code&gt;. Ours returns the tool list without a token, so the portal announced: "Server requires no auth to list and call tools." The first half is true. The second isn't. You can see our tools without authorization, but you can't call any of them. We still had to select OAuth manually.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The docs disagreed with the live form in four places.&lt;/strong&gt; The &lt;a href="https://claude.com/docs/connectors/building/submission" rel="noopener noreferrer"&gt;docs&lt;/a&gt; promised eleven steps; we got ten. The icon field was described as a file upload but turned out to want a URL. The best one was the listing address: the docs use &lt;code&gt;claude.ai/directory/connectors/&amp;lt;slug&amp;gt;&lt;/code&gt;, while the actual listing is &lt;code&gt;claude.ai/directory/&amp;lt;slug&amp;gt;&lt;/code&gt;. The old version even appeared in the approval email. We nearly copied it into our own documentation because apparently an email can still outrank the browser in my head.&lt;/p&gt;

&lt;h3&gt;
  
  
  The rake half the ecosystem steps on
&lt;/h3&gt;

&lt;p&gt;One deserves its own section, because I went looking to see whether we'd done something stupid. This time, at least, we had company.&lt;/p&gt;

&lt;p&gt;The portal flagged all eighteen of our tools as "missing a title." All eighteen had titles. The problem is that the MCP spec has two places for &lt;code&gt;title&lt;/code&gt;: top-level &lt;code&gt;Tool.title&lt;/code&gt;, &lt;a href="https://modelcontextprotocol.io/specification/2025-06-18/server/tools" rel="noopener noreferrer"&gt;added in summer 2025&lt;/a&gt;, and the older &lt;code&gt;annotations.title&lt;/code&gt;. We used the newer field. &lt;strong&gt;The Claude directory was checking &lt;code&gt;annotations.title&lt;/code&gt;.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This has caught other people too. A &lt;a href="https://github.com/PrefectHQ/fastmcp/pull/5217" rel="noopener noreferrer"&gt;pull request to FastMCP&lt;/a&gt; describes the same problem: its titles were going only into the top-level field, so the Claude connectors directory saw the tools as nameless. If the portal says your tools are missing titles and you can clearly see the titles in the schema, check &lt;code&gt;annotations.title&lt;/code&gt; before doing anything more dramatic. That string is also what users see in the confirmation dialog, so they get "Create post" instead of &lt;code&gt;create_post&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  A couple of smaller rakes, also worth taking with you
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;The form draft doesn't save.&lt;/strong&gt; We went through all ten steps, clicked "Save and exit," and lost the lot. When we came back, the form was empty and the dashboard said we hadn't submitted any servers. "Draft saved in this tab" is apparently very literal. I'd do the form in one sitting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The "Keep your personal account separate" checkbox when creating an organization.&lt;/strong&gt; Without it, your personal subscription is cancelled and your chat history moves into the organization, with no way to move it back. It's an easy checkbox to skim past and not a particularly easy decision to undo.&lt;/p&gt;

&lt;h3&gt;
  
  
  About what I learned myself
&lt;/h3&gt;

&lt;p&gt;Separately, because this part was my problem rather than Anthropic's. Along the way I put four things into our blockers: a network supposedly missing from the test account, a strange 403, dead tokens and a parameter I thought we had to change. None survived an actual live check. In several cases I'd looked at one field in the database, decided what it meant and promoted the guess to a problem.&lt;/p&gt;

&lt;p&gt;There was a good example in the other direction too. At one point I entered the wrong plan, the one I thought was required for submission. Claude accepted that as a fact, built the rest of the reasoning around it and very confidently helped me proceed in the wrong direction. Which was fair enough. I had given it bad information and then asked it to reason from there.&lt;/p&gt;

&lt;p&gt;I'm getting better at noticing this when we work together: when Claude has started building on an assumption, when it's doing something I didn't actually ask for, and when I need to stop and check the boring fact underneath all the reasoning. The same applies to me. My "I think there's a defect here" can become a blocker in a document surprisingly quickly. Claude's guess can do the same. Neither becomes more factual because it has been written down neatly.&lt;/p&gt;

&lt;h3&gt;
  
  
  What to do with this yourself
&lt;/h3&gt;

&lt;p&gt;Publora is &lt;a href="https://claude.ai/directory/publora" rel="noopener noreferrer"&gt;in the Claude directory&lt;/a&gt; now. You can connect it and create posts for ten social networks from the same chat where you're already working, instead of moving the text into another app afterward.&lt;/p&gt;

&lt;p&gt;The part I'd still be careful with is delegation. I've &lt;a href="https://dev.to/eugeniya_ivanova_4a58eadc/the-agent-posted-successfully-to-the-wrong-account-3kf3"&gt;written about this before&lt;/a&gt;, but it keeps coming up: vague instructions give Claude room to fill things in. If you want a post published, tell it what to publish, where and when. If you want a draft, say draft. I don't rely on Claude to guess which parts of a publishing decision I meant to leave unsaid.&lt;/p&gt;

&lt;p&gt;Looking back at the things I've built this year, there's a slightly ridiculous amount of technical work for someone who still introduces herself by saying she's not an engineer. Claude has been involved in most of it. So seeing Publora in its directory was nice in a way I hadn't expected.&lt;/p&gt;

&lt;p&gt;The actual submission took fifteen minutes. Getting to the point where those fifteen minutes were possible took considerably longer.&lt;/p&gt;




&lt;p&gt;I wrote this one with Claude as well, which seemed only fair given where it ended up. I told it what happened, it drafted, and I cut whatever hadn't actually happened.&lt;/p&gt;

&lt;p&gt;Have you ever spent months getting somewhere and then found the actual moment itself was almost suspiciously uneventful?&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>ai</category>
      <category>claude</category>
      <category>api</category>
    </item>
    <item>
      <title>I was sure Figma would reject my cover art. The email asked about something else.</title>
      <dc:creator>Eugeniya Ivanova</dc:creator>
      <pubDate>Mon, 21 Sep 2026 07:32:45 +0000</pubDate>
      <link>https://dev.to/eugeniya_ivanova_4a58eadc/i-was-sure-figma-would-reject-my-cover-art-the-email-asked-about-something-else-2j4k</link>
      <guid>https://dev.to/eugeniya_ivanova_4a58eadc/i-was-sure-figma-would-reject-my-cover-art-the-email-asked-about-something-else-2j4k</guid>
      <description>&lt;p&gt;A few years ago, my Figma skills were roughly limited to whatever I could make from templates designer friends gave me. Sometimes I made silly pictures for colleagues. A normal user, basically: I can do a few things, and if I can't, I probably don't need to be there.&lt;/p&gt;

&lt;p&gt;Then I changed jobs, and somehow this escalated. Sometimes I fix a layout myself, sometimes I make an agent do it because spending half an hour figuring out where Figma hid one particular effect is usually beyond both my time and my patience. And then I ended up in a fairly strange place: I wasn't just using other people's Figma plugins anymore. I'd &lt;a href="https://www.figma.com/community/plugin/1673632240752253298/publora-publish-to-social" rel="noopener noreferrer"&gt;made my own&lt;/a&gt;. It lets you send a design from Figma straight to your social accounts without exporting it and dragging it through three other apps first.&lt;/p&gt;

&lt;p&gt;What a wonderful world. You never know where it'll take you.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where I got stuck, and it wasn't where I expected
&lt;/h3&gt;

&lt;p&gt;Writing the plugin wasn't the scary part. I got stuck on the pictures.&lt;/p&gt;

&lt;p&gt;To publish a plugin, Figma asks for an icon, a cover and screenshots. The cover stopped me. Make it myself in Figma, where I am, let's put it gently, not very good? Give it to an agent? Do both and then spend ages choosing between things I'm not qualified to judge? I spent an embarrassing amount of time on this.&lt;/p&gt;

&lt;p&gt;The problem was stupid, but real. I was about to submit a plugin to a platform full of designers. Actual designers. And here I come with my cover. People will look at it. People will know.&lt;/p&gt;

&lt;p&gt;In the end, Claude made the cover. Simple, clean, no attempt on my part to demonstrate the designer apparently hiding somewhere inside me. I submitted the plugin and waited to be told what was wrong with the pictures.&lt;/p&gt;

&lt;h3&gt;
  
  
  The email came. About something else.
&lt;/h3&gt;

&lt;p&gt;Figma's review was nothing like the others I'd been through. Chrome took two rounds. Adobe sent us back with changes too. Zapier had eight. By then I'd come to think of moderation as a kind of correspondence where somebody explains what you've done wrong and you write back when you've fixed it.&lt;/p&gt;

&lt;p&gt;Figma said nothing. Then an email arrived: send us a video showing how the plugin works.&lt;/p&gt;

&lt;p&gt;That was it.&lt;/p&gt;

&lt;p&gt;Not "the cover is weak." Not "the icon needs work." Not even "who let you in here." They wanted to see that the plugin did what it was supposed to do.&lt;/p&gt;

&lt;p&gt;We recorded the video and sent it. Silence again. Then the plugin appeared in the Community. It even has a couple of users already, which I particularly like because it means we're no longer the only people using the thing we made.&lt;/p&gt;

&lt;p&gt;Only after that did I read &lt;a href="https://help.figma.com/hc/en-us/articles/360039958914-Plugin-and-widget-review-guidelines" rel="noopener noreferrer"&gt;Figma's review rules&lt;/a&gt;, which would obviously have been more useful before I started worrying. There's nothing in there about how the cover looks. They review whether the plugin is finished, whether it crashes, whether it does what the listing says, whether it's safe with someone's files. The only line that touches the pictures at all asks for an accurate description, screenshot, or previews, "so that the users will not be surprised by any hidden functionality." Not a good cover. An honest one.&lt;/p&gt;

&lt;p&gt;So I'd spent several days worrying about a test nobody was planning to give me.&lt;/p&gt;

&lt;h3&gt;
  
  
  And this is where it gets a little strange
&lt;/h3&gt;

&lt;p&gt;I didn't become a designer along the way. I didn't become an engineer either. But there's a plugin in the Figma Community now, and it has a perfectly decent cover.&lt;/p&gt;

&lt;p&gt;A few years ago, "make a cover for a plugin" would have meant learning how to make covers first. Now I can explain what I need to an agent, get a few options, throw away the terrible ones and keep something decent. This does not make me a designer. It does, however, leave me with a cover.&lt;/p&gt;

&lt;p&gt;I'm still not entirely sure what I think about that. Taste and a good eye obviously still matter: if you're given five bad options, somebody has to notice they're bad. But I also quite like living in a world where needing one particular effect doesn't automatically mean watching a forty-minute tutorial about it.&lt;/p&gt;

&lt;p&gt;My actual job is getting people to use our product. I'm not a designer or an engineer. But somewhere in the Figma Community there's now our plugin, with a cover I couldn't have made myself.&lt;/p&gt;

&lt;p&gt;Well. Apparently I can make Figma plugins now.&lt;/p&gt;




&lt;p&gt;I wrote this one with Claude too, which feels about right for a post that's partly about not making things myself. I described what happened, it drafted, and I cut the parts that weren't true.&lt;/p&gt;

&lt;p&gt;Has AI ever dropped you somewhere the old rules say you don't belong?&lt;/p&gt;

</description>
      <category>figma</category>
      <category>ai</category>
      <category>design</category>
      <category>career</category>
    </item>
    <item>
      <title>I didn't come to Claude through code. I came through a 10 a.m. routine.</title>
      <dc:creator>Eugeniya Ivanova</dc:creator>
      <pubDate>Fri, 18 Sep 2026 07:28:48 +0000</pubDate>
      <link>https://dev.to/eugeniya_ivanova_4a58eadc/i-didnt-come-to-claude-through-code-i-came-through-a-10-am-routine-3e14</link>
      <guid>https://dev.to/eugeniya_ivanova_4a58eadc/i-didnt-come-to-claude-through-code-i-came-through-a-10-am-routine-3e14</guid>
      <description>&lt;p&gt;Most people I know got to Claude through code, which is reasonable enough considering the name. I got there because at ten in the morning I wanted somebody else to check my numbers.&lt;/p&gt;

&lt;p&gt;I'm not an engineer. My job is getting people to use our product, which means I spend a surprising amount of time looking at numbers, looking at people, and then looking at the numbers again because I no longer trust what I saw the first time. I started with Claude Desktop, then tried Cowork, and eventually found &lt;a href="https://docs.claude.com/en/docs/claude-code/routines" rel="noopener noreferrer"&gt;routines&lt;/a&gt;. I have three now. None is particularly impressive, which is probably why I actually use them.&lt;/p&gt;

&lt;h3&gt;
  
  
  The 10 a.m. check
&lt;/h3&gt;

&lt;p&gt;The serious Publora analytics live in Claude Code in VSCode. That's where the keys are, the detailed data, revenue, and all the things capable of ruining an otherwise decent morning. I can go in there and dig properly, but the problem is that I can always dig properly. There is always another cohort to check, another strange number, another question that suddenly seems urgent because I've opened the file containing it. If I do that every morning, there goes the morning.&lt;/p&gt;

&lt;p&gt;So at 10 a.m. Claude sends me a Publora brief: tasks, analytics, meetings, anything that looks unusual. I've normally been at my desk since eight, so by then I've had coffee, answered email and acquired enough emotional stability to look at analytics. If something moved, I open VSCode and investigate. If everything looks normal, I leave it alone. Before this routine, apparently I needed two hours of analysis every now and then to discover that everything was fine.&lt;/p&gt;

&lt;h3&gt;
  
  
  The file where future posts go to wait
&lt;/h3&gt;

&lt;p&gt;The second routine runs Monday, Wednesday and Friday. It looks around my usual topics, finds two or three things worth writing about and puts them in &lt;code&gt;content-backlog.md&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The file turned out to matter. At first I had Claude send me ideas, which produced a very nice list that I would read, approve of and never see again. Now the ideas accumulate in one place. By Friday there are several possible posts in there. Some are bad, some seemed good on Monday and make no sense by Friday, and usually a couple survive.&lt;/p&gt;

&lt;p&gt;That's all I need from it. I don't want Claude writing my posts while I sleep. I want to sit down to write and not spend the first forty minutes discovering that apparently I have never had a thought in my life.&lt;/p&gt;

&lt;h3&gt;
  
  
  Hacker News without going to Hacker News
&lt;/h3&gt;

&lt;p&gt;The third one is technically a &lt;a href="https://docs.claude.com/en/docs/claude-code/scheduled-tasks" rel="noopener noreferrer"&gt;scheduled task&lt;/a&gt; rather than a routine. It gives me a Hacker News digest twice a day. This exists because I know what happens when I tell myself I'll check the news when I have time: either I don't, or I open Hacker News to read one thing, then another, then somebody is wrong in the comments, and forty minutes later I'm extremely well informed about something I wasn't working on.&lt;/p&gt;

&lt;p&gt;Now the digest arrives by itself. I skim it, open whatever actually looks useful and occasionally find something I want to comment on. I remain approximately aware of the world without having to visit quite so much of it.&lt;/p&gt;

&lt;h3&gt;
  
  
  My routines stop when I close the laptop
&lt;/h3&gt;

&lt;p&gt;There is one limitation: these routines are local, so my computer has to be awake and online. Claude Code also has cloud Routines that can run while the laptop is closed, which means you can wake up and the work is already there. People quite reasonably describe this as an advantage.&lt;/p&gt;

&lt;p&gt;I understand them intellectually. Personally, I'm an anxious person, and the idea that Claude has been awake since six doing things on my behalf while I was asleep does not relax me. I'd wake up wondering what it had done. Mine start working when I do. I'm at the desk, Claude is at the desk, and nobody has been getting creative overnight. This arrangement suits me.&lt;/p&gt;

&lt;h3&gt;
  
  
  What I actually gave it
&lt;/h3&gt;

&lt;p&gt;None of these routines does anything spectacular. One checks whether something in the numbers needs my attention, one keeps a file of things I might write about, and one reads Hacker News so I don't accidentally spend forty minutes reading Hacker News myself.&lt;/p&gt;

&lt;p&gt;I still make the decisions, which unfortunately seems to be most of my job. What disappeared was the need to remember to go and check all this stuff myself. And I set the whole thing up in plain English, without writing code, which is mildly funny considering how long the word "Code" made me assume this wasn't for me.&lt;/p&gt;

&lt;p&gt;Now I'm much more interested in the boring things people put on routines than the impressive ones.&lt;/p&gt;




&lt;p&gt;I wrote this one with Claude as well: I described what the three routines actually do, it drafted, and I cut the parts that weren't true. Which is more or less the arrangement everywhere else in this post.&lt;/p&gt;

&lt;p&gt;What's the stupid recurring thing you got tired of doing yourself?&lt;/p&gt;

</description>
      <category>ai</category>
      <category>productivity</category>
      <category>automation</category>
      <category>claude</category>
    </item>
    <item>
      <title>IFTTT accepted our service on the first try. The flow broke on the first redirect.</title>
      <dc:creator>Eugeniya Ivanova</dc:creator>
      <pubDate>Wed, 16 Sep 2026 15:30:00 +0000</pubDate>
      <link>https://dev.to/eugeniya_ivanova_4a58eadc/ifttt-accepted-our-service-on-the-first-try-the-flow-broke-on-the-first-redirect-4fjd</link>
      <guid>https://dev.to/eugeniya_ivanova_4a58eadc/ifttt-accepted-our-service-on-the-first-try-the-flow-broke-on-the-first-redirect-4fjd</guid>
      <description>&lt;p&gt;Last week I was bringing Publora onto IFTTT. Our server was already live, OAuth was working, and the API was there. I expected most of the work to be filling in their application and getting through review.&lt;/p&gt;

&lt;p&gt;The application passed on the first try, in under a day instead of the estimated one to two weeks. The actual integration broke on the first authorization redirect.&lt;/p&gt;

&lt;p&gt;Here's what I ran into.&lt;/p&gt;

&lt;h2&gt;
  
  
  The OAuth that didn't line up
&lt;/h2&gt;

&lt;p&gt;The first problem showed up immediately.&lt;/p&gt;

&lt;p&gt;IFTTT sends plain OAuth2: authorization code, no PKCE. Our authorization server requires PKCE. Their redirect arrives without a &lt;code&gt;code_challenge&lt;/code&gt;, so our server rejects it.&lt;/p&gt;

&lt;p&gt;I tried the request in exactly the format IFTTT sends:&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="err"&gt;GET https://mcp.publora.com/authorize
  ?client_id=...&amp;amp;response_type=code&amp;amp;scope=ifttt&amp;amp;state=...&amp;amp;redirect_uri=...

-&amp;gt; 302 ...?error=invalid_request
   "expected string, received undefined" on code_challenge
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add a &lt;code&gt;code_challenge&lt;/code&gt; to the same request and it reaches the login screen normally. So there wasn't much mystery left: IFTTT doesn't send something our authorization server requires.&lt;/p&gt;

&lt;p&gt;I could either make PKCE optional on our server for IFTTT or put something between the two. I went with the second option.&lt;/p&gt;

&lt;p&gt;I added a small Cloudflare Worker between IFTTT and Publora. To IFTTT, it looks like the plain OAuth2 provider it expects: no PKCE and a non-expiring token. On the Publora side, the Worker runs the PKCE flow itself, generating the verifier and challenge before sending the request to our authorization server.&lt;/p&gt;

&lt;p&gt;That also gave me somewhere to handle the rest of the differences between the two APIs. IFTTT expects endpoints and responses in its own format, while Publora already has its own API. The Worker translates between them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Turning posts into an event feed
&lt;/h2&gt;

&lt;p&gt;IFTTT treats a trigger as a feed rather than a current state: up to fifty recent events, newest first, each with its own &lt;code&gt;meta.id&lt;/code&gt; and &lt;code&gt;meta.timestamp&lt;/code&gt; in seconds.&lt;/p&gt;

&lt;p&gt;For Publora that means one post group may need to become several events, one for each channel. Otherwise the &lt;code&gt;meta.id&lt;/code&gt; isn't unique:&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;for &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;post&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;posts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;target&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;platforms&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;account&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;label&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;connections&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;platformId&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="na"&gt;network&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;platform&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;occurred_at&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;at&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;meta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;post&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;postGroupId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;platformId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;seconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;at&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;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;There's another requirement they test separately: every trigger needs to return at least three events during testing, so it behaves like a feed rather than a state sensor. An empty or short response doesn't pass.&lt;/p&gt;

&lt;p&gt;I also replaced hourly polling with realtime notifications. A Publora webhook is created when the connection is set up, and when an event arrives the Worker calls IFTTT's Realtime API. It doesn't send the event data there; it just tells IFTTT there's something new, and IFTTT fetches the feed itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Five things I couldn't find in their docs
&lt;/h2&gt;

&lt;p&gt;This is where I spent most of the time. If you're building an IFTTT service, these are worth knowing before you start running their tests.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The &lt;code&gt;test/setup&lt;/code&gt; endpoint is required, but I couldn't find it in the docs.&lt;/strong&gt; Their automated tests hit it second. There is a sample response in the dashboard under View scaffold JSON: it needs a &lt;code&gt;queries&lt;/code&gt; section, and &lt;code&gt;media_url&lt;/code&gt; can't be an empty string. The method isn't specified there, so ours accepts both GET and POST.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ingredient slugs can double without showing it in the UI.&lt;/strong&gt; The form generates a slug while you're typing the field name, and entering your own can leave you with things like &lt;code&gt;contentcontent&lt;/code&gt; and &lt;code&gt;occurred_atoccurred_at&lt;/code&gt;. The resulting slug isn't visible in the form. I only found it in the service export under Tools, Export. A good chunk of my failed tests came from this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The UI didn't show me why tests were failing.&lt;/strong&gt; I could expand the failed result row, but it was empty. The actual results were available through these endpoints:&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="err"&gt;POST /services/&amp;lt;slug&amp;gt;/publish/endpoint_session   -&amp;gt; session_id
GET  /services/&amp;lt;slug&amp;gt;/publish/endpoint_test_results?offset=N&amp;amp;session_id=...
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The results come in chunks, with &lt;code&gt;suite_end&lt;/code&gt; marking the end. That's where I eventually found the individual failed checks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A request with no fields needs to return a proper 400.&lt;/strong&gt; They test requests with &lt;code&gt;triggerFields&lt;/code&gt; missing and with required keys missing inside it. The response needs an &lt;code&gt;errors&lt;/code&gt; array. I initially supplied defaults in some of these cases, which made the tests fail.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The cursor is mandatory when results are limited.&lt;/strong&gt; If you return only part of the feed because of a limit, the response also needs a &lt;code&gt;cursor&lt;/code&gt;. Without it, the test fails.&lt;/p&gt;

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

&lt;p&gt;Their run has 535 checks. I eventually got it to zero failures, after spending considerably more time than I'd like to admit on doubled slugs and test results hidden behind an empty UI row.&lt;/p&gt;

&lt;p&gt;There are also two launch requirements I hadn't found in the docs: twelve published applets and four service admins. You can submit the service without them, but you need them before launch.&lt;/p&gt;

&lt;p&gt;I submitted on September 7 and the review passed on September 8, so under a day against the estimated one to two weeks.&lt;/p&gt;

&lt;p&gt;The Worker ended up at about 700 lines of TypeScript and a 19.8 KB bundle.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd check first next time
&lt;/h2&gt;

&lt;p&gt;If you're doing an IFTTT integration, check the generated slugs in the service export rather than trusting the form. If an automated test fails without an explanation in the UI, look at &lt;code&gt;endpoint_test_results&lt;/code&gt;. And before running the full suite, make sure you have &lt;code&gt;test/setup&lt;/code&gt;, cursors on limited responses, and proper 400 responses for missing fields.&lt;/p&gt;

&lt;p&gt;For OAuth, I was glad I didn't make PKCE optional in Publora just to accommodate this integration. The Worker was already needed to translate IFTTT's API format, so handling the OAuth difference there kept the change isolated too.&lt;/p&gt;

&lt;p&gt;I built it with Claude: it wrote most of the TypeScript, while I tested the requests in IFTTT's format and worked out where the two sides disagreed. Most of the time went into finding those disagreements, especially the ones I couldn't see from the dashboard.&lt;/p&gt;

&lt;p&gt;The listing is here: &lt;a href="https://ifttt.com/publora" rel="noopener noreferrer"&gt;https://ifttt.com/publora&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you've integrated with IFTTT before and found another undocumented test, I'd like to know which one.&lt;/p&gt;

</description>
      <category>ifttt</category>
      <category>oauth</category>
      <category>api</category>
      <category>webdev</category>
    </item>
    <item>
      <title>I gave an agent my posting history. It found a promise I never made.</title>
      <dc:creator>Eugeniya Ivanova</dc:creator>
      <pubDate>Tue, 08 Sep 2026 07:10:41 +0000</pubDate>
      <link>https://dev.to/eugeniya_ivanova_4a58eadc/i-gave-an-agent-my-posting-history-it-found-a-promise-i-never-made-4n62</link>
      <guid>https://dev.to/eugeniya_ivanova_4a58eadc/i-gave-an-agent-my-posting-history-it-found-a-promise-i-never-made-4n62</guid>
      <description>&lt;p&gt;Every social platform rewards the same thing, and it isn't a secret: showing up regularly. The hard part isn't writing. It's deciding what to say today, and on plenty of days there either isn't an answer or you don't feel like hunting for one.&lt;/p&gt;

&lt;p&gt;Someone I was talking to on LinkedIn last week told me they stick with their scheduling tool mostly because of the post templates. When they run out of ideas, the templates give them somewhere to start. That surprised me. I would have guessed people got stuck on the writing, not on choosing the topic.&lt;/p&gt;

&lt;p&gt;Asking an agent doesn't really fix that either, at least not in the usual setup. You say "write me a post," it gives you one draft based on its first guess, and if you're not convinced, there's often nothing specific to react to. You end up rewriting it yourself, which rather defeats the point.&lt;/p&gt;

&lt;p&gt;So this week I built a small skill around one very narrow idea: don't give me a draft first. Give me three angles and let me pick. Three options I can answer with a number.&lt;/p&gt;

&lt;p&gt;It works now. The first real run didn't. It failed three times before it gave me anything useful, and all three failures came from the same place: the agent was reading my own data and treating its interpretation as fact.&lt;/p&gt;

&lt;h3&gt;
  
  
  It read my test posts as things my audience saw
&lt;/h3&gt;

&lt;p&gt;The skill starts by calling &lt;code&gt;list_posts&lt;/code&gt; and reading the last twenty. It's looking for two things: topics I've already covered, so it doesn't suggest the same thing again, and threads I've left hanging. A launch with no follow-up. A question I asked and never came back to. Those are often more useful than inventing a new topic because the context already exists and I usually have the material.&lt;/p&gt;

&lt;p&gt;It found one. Two posts on my LinkedIn from late July said Publora was coming to Zapier. Nothing after that. So it suggested: finish the thread, you promised this a month ago and never said how it turned out.&lt;/p&gt;

&lt;p&gt;Except I hadn't promised anything.&lt;/p&gt;

&lt;p&gt;Those posts were Zapier review artifacts. To submit an app there, you have to run every trigger and action inside a live Zap, switch it on, and leave a successful run in the history. That means my account contains posts with names like &lt;code&gt;Zapier validation — update target&lt;/code&gt;. They look like normal posts in the same list, and the agent had no idea they weren't meant for an audience.&lt;/p&gt;

&lt;p&gt;I added a filter: short posts, near-duplicates minutes apart, anything containing &lt;em&gt;test&lt;/em&gt; or &lt;em&gt;validation&lt;/em&gt;. But the more useful fix was a rule:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Never state an inference from history as a fact.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;"You promised X and never followed up" sounds confident, but it's still an inference from a list that includes QA junk. The agent should say what it found and ask whether it understood it correctly. Once I corrected it, the whole session changed direction in one message. If I hadn't noticed, I could easily have published a post referring to a promise nobody ever saw.&lt;/p&gt;

&lt;h3&gt;
  
  
  It could only see what went through the product
&lt;/h3&gt;

&lt;p&gt;Once we had the right angle, the skill asked for the one fact it couldn't know: what actually happened with Zapier. I told it we were live but still in beta.&lt;/p&gt;

&lt;p&gt;What it didn't ask was whether I'd already written about it somewhere else. I had. That morning I'd published a four-minute Dev.to article with the eight rounds of review, the REST Hooks, and the JavaScript I ended up putting in a field labeled "label". None of that existed in Publora, so the skill had no way to know about it.&lt;/p&gt;

&lt;p&gt;That limitation is obvious in theory: an agent only knows the data you give it. In practice, it's easy to forget until it starts repeating something you've already published elsewhere.&lt;/p&gt;

&lt;p&gt;So now, whenever it asks for a missing fact, it also asks whether I've already written about the topic somewhere else: article, changelog, release notes, whatever.&lt;/p&gt;

&lt;h3&gt;
  
  
  The slop checker passed a draft that was still slop
&lt;/h3&gt;

&lt;p&gt;I run every outgoing text through a script that flags machine-sounding writing: vocabulary, repeated structures, the usual tells that become easy to spot once you've collected enough of them.&lt;/p&gt;

&lt;p&gt;The draft passed.&lt;/p&gt;

&lt;p&gt;I showed it to a colleague anyway, and she said she could still see AI slop in it. She was right.&lt;/p&gt;

&lt;p&gt;Three of the strongest lines had been lifted almost directly from my Dev.to article from that morning. "The requirement I reread three times, certain I'd misunderstood." Fine line on its own. Less fine when the same person has already seen it six hours earlier. No vocabulary checker is going to catch that.&lt;/p&gt;

&lt;p&gt;The other problems were structural. A withheld hook: "with a Beta tag, which I'll get to." A neat paradox: "you have to prove the app is being used before it's allowed to exist." A three-item list whose last item worked mostly because of the rhythm. The word "literally". None of those is automatically bad. Together, in a short post, they started sounding very familiar.&lt;/p&gt;

&lt;p&gt;The rewrite was 720 characters. The version that passed the script was 1198.&lt;/p&gt;

&lt;p&gt;So I kept the checker, but moved it earlier in the process. It catches the things that exist inside the text. What it can't see is everything around the text: what I published yesterday, who is reading, or whether I've stacked too many individually reasonable choices into something that sounds generated.&lt;/p&gt;

&lt;p&gt;I'd still put a checker somewhere between draft and publish. It costs very little once it exists, catches the obvious layer, and doesn't get tired at six in the evening. Build one, borrow one, or use a checklist. Just don't mistake it for the whole review.&lt;/p&gt;

&lt;h3&gt;
  
  
  What it looks like now
&lt;/h3&gt;

&lt;p&gt;There are eight categories and forty angles. Every angle has a &lt;strong&gt;core&lt;/strong&gt;: one fact that has to come from the person using the skill, whether that's the mistake, the number, or the tool name. The instructions repeat this in several places because inventing that core is the easiest way to produce something plausible and false under someone's name.&lt;/p&gt;

&lt;p&gt;There are also four reference files behind the angles: what the first two lines need to do before the feed collapses the rest, how to infer voice from existing posts instead of asking someone to describe their own voice, the tells a script can't catch, and how the same angle changes between LinkedIn and X.&lt;/p&gt;

&lt;p&gt;It stops at the draft. Nothing publishes automatically.&lt;/p&gt;

&lt;p&gt;It's MIT, it works on its own, and connecting Publora lets it read your feed and schedule the result: &lt;a href="https://github.com/publora-team/publora-post-ideas" rel="noopener noreferrer"&gt;publora-team/publora-post-ideas&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;For the record, I'm not an engineer. My job is getting people to use our product, and I built this with Claude: it wrote the code, I led, tested, and sent it back when it got things wrong. Most of the useful work ended up being in those three failures, because each one showed me a rule I hadn't written yet.&lt;/p&gt;

&lt;p&gt;Has an agent ever been confidently wrong about your own data? I'm curious what it misunderstood, and what you changed after that.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>writing</category>
      <category>socialmedia</category>
    </item>
    <item>
      <title>My MCP integration got rejected. Almost nothing in the server had to change.</title>
      <dc:creator>Eugeniya Ivanova</dc:creator>
      <pubDate>Mon, 07 Sep 2026 07:54:22 +0000</pubDate>
      <link>https://dev.to/eugeniya_ivanova_4a58eadc/my-mcp-integration-got-rejected-almost-nothing-in-the-server-had-to-change-npb</link>
      <guid>https://dev.to/eugeniya_ivanova_4a58eadc/my-mcp-integration-got-rejected-almost-nothing-in-the-server-had-to-change-npb</guid>
      <description>&lt;p&gt;In July I set out to add our MCP server to the ChatGPT app directory. The server was already live, running in Claude and Cursor, with OAuth working. Submitting looked like a formality: describe what already exists, fill in the application, hit Submit.&lt;/p&gt;

&lt;p&gt;A month and one rejection later, almost nothing had changed in the server itself. It had been working the whole time. What we ended up fixing was everything around it: how ChatGPT discovers the tools, what the scanner expects, what we'd written in the descriptions, and whether a reviewer could actually log in.&lt;/p&gt;

&lt;p&gt;Here's what caught me.&lt;/p&gt;

&lt;h3&gt;
  
  
  The tool list that freezes solid
&lt;/h3&gt;

&lt;p&gt;The first problem came before submission. I connected the server to ChatGPT — the connector was created, but it showed "no actions available." Zero tools. In Claude and Cursor the same tools loaded fine.&lt;/p&gt;

&lt;p&gt;I spent a while looking at SSE and caching. Neither was the problem. ChatGPT reads the tool list when the app is created, and if the server returns zero at that point, the app can get stuck there. "Refresh" is disabled at zero and "Save" is greyed out. In my case, the way out was to delete the app and create it again.&lt;/p&gt;

&lt;p&gt;The server returned zero because it required a token for &lt;code&gt;tools/list&lt;/code&gt; itself. Claude logs in first and fetches the list afterwards, which is why we'd never seen the problem there. ChatGPT fetches the list before authorization, gets nothing, and turns to stone. So now I check one thing before doing anything else: &lt;code&gt;tools/list&lt;/code&gt; needs to return 200 with the tools and no token.&lt;/p&gt;

&lt;h3&gt;
  
  
  Annotations the scanner demands and the spec doesn't
&lt;/h3&gt;

&lt;p&gt;Next, OpenAI's scanner went through every tool and required explicit &lt;code&gt;readOnlyHint&lt;/code&gt;, &lt;code&gt;openWorldHint&lt;/code&gt; and &lt;code&gt;destructiveHint&lt;/code&gt; values. Four read-only tools didn't have &lt;code&gt;destructiveHint&lt;/code&gt;, which made sense according to &lt;a href="https://modelcontextprotocol.io/specification/2025-06-18/server/tools" rel="noopener noreferrer"&gt;the MCP spec&lt;/a&gt;: it only matters when &lt;code&gt;readOnlyHint: false&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The scanner wanted it anyway. We added &lt;code&gt;destructiveHint: false&lt;/code&gt; to those tools and wrote justifications for forty-odd annotation values. The tools themselves didn't change. We just had to make explicit something the spec allowed us to leave implied.&lt;/p&gt;

&lt;h3&gt;
  
  
  The domain challenge that wants a bare string and lives forever
&lt;/h3&gt;

&lt;p&gt;A small one that's easy to waste time on: OpenAI's domain verification expects the endpoint to return a bare token string. No JSON, no quotes. Return &lt;code&gt;{"token": "..."}&lt;/code&gt; and it fails.&lt;/p&gt;

&lt;p&gt;It also isn't a one-time verification endpoint. It has to stay there in production, answering the same way afterwards. So we now have a tiny endpoint with one permanent job: return a string when OpenAI asks.&lt;/p&gt;

&lt;h3&gt;
  
  
  The drift I caught on myself
&lt;/h3&gt;

&lt;p&gt;Then I found something that actually was our mistake.&lt;/p&gt;

&lt;p&gt;Our public &lt;a href="https://github.com/publora-team/mcp-server" rel="noopener noreferrer"&gt;&lt;code&gt;mcp-server&lt;/code&gt; repo&lt;/a&gt; defines 18 tools, four of them for LinkedIn analytics: post stats, account stats, followers and profile summary. The live server exposes 16, and none of those analytics tools are among them. They exist in the REST API, but aren't exposed through MCP.&lt;/p&gt;

&lt;p&gt;The public repo had fallen behind the live server, and the app description was based on the repo. So we were describing analytics that someone using MCP couldn't actually access. I removed them from the description and release notes.&lt;/p&gt;

&lt;p&gt;Then I found the same old description in two more places: the README for the Zed extension and the PR for the Docker MCP Catalog. The Zed PR was also pointing at a commit from before the correction, so merging it would have put the outdated claim into another catalog. We'd updated the product and missed a few places where we'd described it. Easy enough to do when those descriptions live in different repos and submissions.&lt;/p&gt;

&lt;h3&gt;
  
  
  The rejection that wasn't about the code
&lt;/h3&gt;

&lt;p&gt;Submitted August 5, every wizard step completed in one pass. Rejected August 24.&lt;/p&gt;

&lt;p&gt;The message was: "We're unable to complete your sign-in or OAuth flow… ensure valid, working credentials… no additional setup or verification."&lt;/p&gt;

&lt;p&gt;Before changing anything, I walked through the flow again. Dynamic client registration returned 201. &lt;code&gt;/authorize&lt;/code&gt; sent me to our consent screen. The sign-in page in a clean browser was a normal email-and-password form with no captcha. &lt;code&gt;tools/list&lt;/code&gt; without a key returned the tools. API and MCP access were enabled on every plan, including free. Everything worked.&lt;/p&gt;

&lt;p&gt;Then we looked at the account we'd given the reviewer.&lt;/p&gt;

&lt;p&gt;Our own accounts use Google sign-in. A reviewer can't use our Google account, and even if they tried, the second factor would land on our phone. We'd managed to give them credentials for an account they had no way to get into.&lt;/p&gt;

&lt;p&gt;Even worse, Canva had rejected us for the same reason before. Twice was enough to stop calling it bad luck. We simply didn't have "can a stranger actually use these credentials?" on our submission checklist.&lt;/p&gt;

&lt;h3&gt;
  
  
  What actually fixed it
&lt;/h3&gt;

&lt;p&gt;We created a separate reviewer account: email and password, email confirmed ahead of time, a couple of channels connected, with some posts and drafts already there. We also included eight test cases — five positive and three negative.&lt;/p&gt;

&lt;p&gt;The most useful one was a full write-path test through &lt;code&gt;publora-playground&lt;/code&gt;. It's a fake publishing target: it validates the post against the same rules as a real one and returns the same kind of response, but throws the request away at the end instead of publishing it anywhere. A reviewer can go through the whole publishing flow without putting a test post on someone's actual timeline.&lt;/p&gt;

&lt;p&gt;It took about thirty lines and ended up being one of the most useful things we added for the submission. The reviewer could finally test writes without needing a disposable social account.&lt;/p&gt;

&lt;p&gt;We resubmitted on the evening of August 24. &lt;a href="https://chatgpt.com/plugins/plugin_asdk_app_6a7074e91c008191942479b885fb0d9b" rel="noopener noreferrer"&gt;Approved September 4.&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  What I wrote down for next time
&lt;/h3&gt;

&lt;p&gt;The main addition to my submission checklist is pretty basic: go through the product as the reviewer, not as yourself.&lt;/p&gt;

&lt;p&gt;The server worked in Claude and Cursor. It followed the spec. OAuth worked when we tested it. But ChatGPT asked for the tool list in a different order, its scanner expected metadata we hadn't needed elsewhere, and the reviewer needed a login that didn't depend on being us. Those were the things that took the month.&lt;/p&gt;




&lt;p&gt;I did this with Claude — it wrote the code and configs, while I checked what was happening and decided what needed changing. The funny part is that almost none of those decisions involved changing the server itself. Most of the work ended up being metadata, descriptions and access.&lt;/p&gt;

&lt;p&gt;What's gotten your product bounced from a platform review — something actually broken, or something you never thought a reviewer would run into?&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>ai</category>
      <category>api</category>
      <category>webdev</category>
    </item>
    <item>
      <title>I sell social media automation. My survey says people want the opposite.</title>
      <dc:creator>Eugeniya Ivanova</dc:creator>
      <pubDate>Thu, 03 Sep 2026 08:07:39 +0000</pubDate>
      <link>https://dev.to/eugeniya_ivanova_4a58eadc/i-sell-social-media-automation-my-survey-says-people-want-the-opposite-13e1</link>
      <guid>https://dev.to/eugeniya_ivanova_4a58eadc/i-sell-social-media-automation-my-survey-says-people-want-the-opposite-13e1</guid>
      <description>&lt;p&gt;I work at &lt;a href="https://publora.com" rel="noopener noreferrer"&gt;Publora&lt;/a&gt; — an API and MCP for publishing to social networks from one place, including through an AI agent. So, in a sense, my job is selling automation for posting. Which made the answers I started getting from actual people a little awkward.&lt;/p&gt;

&lt;p&gt;I've been doing a small study — nothing fancy, just messaging people directly and asking: what do you use to post, and why do you do it that way? No pitch, no setup. It's an ongoing thing, reaching out to people across different niches one by one, so this isn't a clean statistic — more a pile of honest answers I'm still adding to. A few hundred replies in, and I'm still going.&lt;/p&gt;

&lt;p&gt;The answers are all over the place, but one thing keeps coming up. People aren't asking for more automation. Quite a lot of them want to keep the final step for themselves.&lt;/p&gt;

&lt;p&gt;A few answers, anonymized.&lt;/p&gt;

&lt;p&gt;One person told me: "I use AI rarely, do the core work myself. Sometimes I go into ChatGPT or Gemini as a 'second brain' — when I'm out of ideas or a deadline's closing in. But at the end of the day it's still just a machine."&lt;/p&gt;

&lt;p&gt;So AI is useful to think with. That doesn't mean they want it doing everything.&lt;/p&gt;

&lt;p&gt;Another: "I use Claude, but not for posting — it sort of sets me up, and I publish myself."&lt;/p&gt;

&lt;p&gt;And a third was very direct: "I'm wary of AI tools having access to my account. LinkedIn isn't forgiving about it."&lt;/p&gt;

&lt;p&gt;That one wasn't really about whether AI-written posts sound authentic. They simply didn't want another tool having that much access to their account.&lt;/p&gt;

&lt;p&gt;Then there was this: "I like hitting publish myself. In a world where everything's polished, it makes the content feel a little more alive."&lt;/p&gt;

&lt;p&gt;Different reasons: sometimes security, sometimes control, sometimes just liking the fact that there's still a person involved. But I kept hearing versions of the same thing. Draft for me, help me think, prepare it — fine. I'll publish it.&lt;/p&gt;

&lt;p&gt;Nobody was dreaming about an agent happily posting all day while they went for coffee.&lt;/p&gt;

&lt;p&gt;At first this was slightly uncomfortable to hear, considering what I do for a living. But the more replies I read, the less it sounded like an objection to automation. It sounded more like people being quite specific about which parts they actually want automated.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where that caution stops being a quirk
&lt;/h3&gt;

&lt;p&gt;One answer stood out because it came from someone managing several brands rather than just their own account.&lt;/p&gt;

&lt;p&gt;They prefer tools with integrations because content and reporting are easier there than doing everything natively. The problem is that not every platform has the integrations they need, and the more channels a brand uses, the messier the whole setup gets.&lt;/p&gt;

&lt;p&gt;That changes the problem quite a bit.&lt;/p&gt;

&lt;p&gt;Being cautious about giving AI access to your own LinkedIn account is one thing. Being an agency responsible for ten clients' accounts is another. Now you're dealing with reputations that aren't yours, client credentials, several calendars, approvals living somewhere in email, and the possibility of someone putting one client's post on another client's account.&lt;/p&gt;

&lt;p&gt;At that point, automating everything sounds terrifying. Doing everything manually isn't much better. Ten clients still have to fit into the same working day.&lt;/p&gt;

&lt;p&gt;So you need something in between.&lt;/p&gt;

&lt;h3&gt;
  
  
  What that looks like in practice
&lt;/h3&gt;

&lt;p&gt;After reading the replies, I kept coming back to three things.&lt;/p&gt;

&lt;p&gt;The first is keeping clients separate. Each one needs their own connections, content and calendar, without the possibility of a post quietly ending up on somebody else's account. A posting mistake on your own account is embarrassing. Doing it to a client's account can get expensive.&lt;/p&gt;

&lt;p&gt;The second is passwords. Ideally, an agency shouldn't have them at all. The client connects their account through an OAuth link, and the team can work with it without ever knowing the login. The person who told me they were wary of tools having access to their account wasn't being paranoid. That's a perfectly reasonable thing to worry about.&lt;/p&gt;

&lt;p&gt;And then there's approval. An agent can draft a post. It can prepare it, schedule it, do the repetitive bits. But publishing can still wait for a person to approve it. In a team, that can also mean the person who wrote the post isn't the person who approves it.&lt;/p&gt;

&lt;p&gt;It's basically the same habit I kept seeing in the survey, only with more people involved.&lt;/p&gt;

&lt;p&gt;We build this at Publora — it's our &lt;a href="https://agencies.publora.com" rel="noopener noreferrer"&gt;workspace for agencies&lt;/a&gt;, and the API is &lt;a href="https://docs.publora.com" rel="noopener noreferrer"&gt;documented here&lt;/a&gt;. I won't turn this into a tour of the product; if that's what you're looking for, the links are there.&lt;/p&gt;

&lt;p&gt;What's been more interesting for me is realizing that some of the things we'd been thinking about as product features map almost exactly to things people told me they were worried about.&lt;/p&gt;

&lt;h3&gt;
  
  
  What I took from it
&lt;/h3&gt;

&lt;p&gt;Automation is usually sold as: let the machine do this for you.&lt;/p&gt;

&lt;p&gt;But after reading these replies, I think there's a pretty big group of people asking for something slightly different: help me do it, just don't take the whole thing away from me.&lt;/p&gt;

&lt;p&gt;For someone posting to their own account, that might simply mean wanting to hit Publish themselves. For an agency, the same instinct turns into much more practical requirements: keep clients separate, don't pass passwords around, and make sure a person approves what goes out.&lt;/p&gt;

&lt;p&gt;I find it slightly funny that it took working inside an automation company to make me think harder about what shouldn't be automated.&lt;/p&gt;




&lt;p&gt;And for the record, I wrote this with AI. It helped me sort through material and clean up the draft. Deciding what was interesting in a few hundred replies was still my job.&lt;/p&gt;

&lt;p&gt;I'm keeping that bit.&lt;/p&gt;

&lt;p&gt;Where do you draw the line? What are you happy to hand to an agent, and what do you still want to do yourself?&lt;/p&gt;

</description>
      <category>ai</category>
      <category>automation</category>
      <category>socialmedia</category>
      <category>discuss</category>
    </item>
    <item>
      <title>I published our app on Zapier. The no-code platform made me write code.</title>
      <dc:creator>Eugeniya Ivanova</dc:creator>
      <pubDate>Mon, 31 Aug 2026 06:29:04 +0000</pubDate>
      <link>https://dev.to/eugeniya_ivanova_4a58eadc/i-published-our-app-on-zapier-the-no-code-platform-made-me-write-code-2hc3</link>
      <guid>https://dev.to/eugeniya_ivanova_4a58eadc/i-published-our-app-on-zapier-the-no-code-platform-made-me-write-code-2hc3</guid>
      <description>&lt;p&gt;Publora is in the Zapier app directory now. I didn't do it to tick a box on some distribution list. My job is making our product easy to live with, and if a user has an agent that can wire us in deeper so they don't have to build the plumbing themselves, I'll go make that happen. Zapier is exactly that case: it connects Publora to thousands of other apps, so nobody has to hand-roll the integration.&lt;/p&gt;

&lt;p&gt;Worth it. I'd just add that "a no-code platform" and "publishing your own app on a no-code platform" turn out to be two very different Zapiers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Prove it works for users who don't exist yet
&lt;/h3&gt;

&lt;p&gt;Here's the requirement I reread three times, sure I'd misunderstood.&lt;/p&gt;

&lt;p&gt;To submit an app for review, every trigger, every action, and every search has to be tested inside a live Zap, turned on, with at least one successful run in the history. You can't delete those Zaps; the reviewer can ask to see them.&lt;/p&gt;

&lt;p&gt;So the logic goes like this. You want to publish an app so people can start using it. But to publish it, you first have to prove it's already being used. Run every component for real, as if you had the users you're publishing it to attract. The app isn't in the directory yet, and a history of real use already has to exist.&lt;/p&gt;

&lt;p&gt;You end up standing in for your own users who aren't there yet. You build the Zaps, run each one, make sure every one has a green run, and don't touch them afterward.&lt;/p&gt;

&lt;h3&gt;
  
  
  A routine task you run like a rocket launch
&lt;/h3&gt;

&lt;p&gt;The second surprise. My tasks here are the plain ones: schedule a post, publish a post, delete a post. This isn't a satellite launch. It's what our API does a thousand times a day over one line of code.&lt;/p&gt;

&lt;p&gt;As a Zapier app, each of those ordinary tasks has to be wrapped, configured, and run live on its own. Create Post, Update Post, Delete Post, two triggers, two searches, each with its own test run under the validator's eye. Scheduling a post is something I can describe in one sentence. Here it became a component with a run history.&lt;/p&gt;

&lt;p&gt;Then the small surprises a "no-code" promise doesn't quite imply. The connection label, the line that tells the user which account they connected, couldn't just read a field. &lt;code&gt;{{connections.0.username}}&lt;/code&gt; doesn't work, because Zapier won't follow a path into an array. So the label needs actual JavaScript:&lt;br&gt;
&lt;/p&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="nx"&gt;connections&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;bundle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;inputData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;connections&lt;/span&gt; &lt;span class="o"&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;first&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;connections&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;first&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Publora&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's in a box labeled "label." On a no-code platform.&lt;/p&gt;

&lt;p&gt;The triggers are the other place the word stretches. These aren't polling. They're REST Hooks on our own webhooks, which means subscribing and unsubscribing are API calls you define by hand:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// subscribe&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;url&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://api.publora.com/api/v1/webhooks&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;headers&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="s1"&gt;Content-Type&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Accept&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;application/json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;x-publora-key&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bundle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;authData&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="p"&gt;},&lt;/span&gt;
  &lt;span class="na"&gt;body&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="s1"&gt;Zapier - Post Published&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bundle&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;targetUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;events&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="s1"&gt;post.published&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="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;throwForStatus&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;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;webhook&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;What subscribe returns is not incidental. Zapier hands it back to you as &lt;code&gt;bundle.subscribeData&lt;/code&gt;, and unsubscribe reads the webhook id out of it to call &lt;code&gt;DELETE /webhooks/{id}&lt;/code&gt;. Return the wrong shape and the Zap turns on cleanly, then never cleans up after itself.&lt;/p&gt;

&lt;p&gt;A few more edges, for anyone about to do this. A field's type and key are locked after you create it, and if you get the shape of a step wrong and you don't fix it, you recreate it from scratch. The first published version gets locked for editing once real people are on it, so you clone it into a new one and make changes there. And the editor session expires with a cheerful &lt;code&gt;failed csrf&lt;/code&gt;; you reload, and the code you just wrote is gone.&lt;/p&gt;

&lt;p&gt;None of it is fatal. I just walked in with a one-line task and walked out with versioning, user migrations, and a chunk of JavaScript in a field called "label."&lt;/p&gt;

&lt;h3&gt;
  
  
  Where it landed
&lt;/h3&gt;

&lt;p&gt;The app passed review after eight rounds of reviewer notes, then out into the directory with a Beta tag. Zapier's beta runs ninety days, and you leave it one of two ways: pull in enough active users, or embed their widget inside your own product. Familiar shape. The platform holds you at "not quite yet," and the key out is either bring it an audience or let it inside your product.&lt;/p&gt;

&lt;p&gt;I don't regret the trip. The directory listing genuinely lowers the effort for anyone who wants to post from whatever workflow they already live in, and I wrote them a separate tutorial for that. But the "no-code" part I left somewhere around the halfway mark. For the user assembling a Zap out of ready-made blocks, sure, no code. For the person making those blocks, no-code ends exactly where anything past a single step begins.&lt;/p&gt;




&lt;p&gt;For the record, I'm not an engineer. My job is getting people to use our product, and I built this with Claude: it wrote the code, I led, tested, and sent it back to redo. Which is maybe the honest shape of "no-code" in 2026 anyway. The code didn't disappear. It just moved to someone else, or something else.&lt;/p&gt;

&lt;p&gt;Did your no-code platforms keep the promise when you went past a single step? Or did you end up opening the code editor too?&lt;/p&gt;

</description>
      <category>zapier</category>
      <category>nocode</category>
      <category>api</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
