Pablo Fernandez's Blog
July 21, 2025
A better monetization model for LLM applications
Bring your own keys into an affiliate relationship
I’m starting to observe a problem where a lot of LLM-enhanced apps are starting to pop up. For coding you have Cursor, but now there’s also a terminal called Warp and it costs $15/month. For individuals, consultants, small and even medium sized companies, this isn’t a workable pricing model. All apps were already turning into subscriptions and the cost of LLMs is accelerating that.
What compounds the problem is that, because everyone feels uncomfortable with the potential surprise high bill of pay-for-what-you-use, many of these apps are charging a single monthly fee. A simple single flat fee, except that the cost of the LLMs is not flat. It grows linear with usage. This means having to throttle the LLM usage to stay within the margin of the flat fee and have a chance at a profit. That means using cheaper models, which yield worse result on average.
I think this is why when I compare Cursor to Claude Code I find Claude Code to be better: I’m giving Anthropic a lot more money than Cursor. But also, I’m happy with that, because I can use Claude in many other ways, where Cursor is a single-use application.
I think from now on, for each LLM powered application, I either want to be able to put my own keys in, or have pay-as-you-go with a lot of transparency. When I need it to work, I want it to work well. When I’m not using it, I want it to cost nothing.
There’s another solution though. LLM providers could have affiliate systems where other companies get a commission for the token usage they generate. Using Warp as an example, Warp wouldn’t ask me for $15/month. Warp would ask me for my Claude API key. Warp would identify all those requests as caused by Warp, and then Anthropic would pay Warp a proportional fee for the usage generated.
This is a win-win-win: Anthropic gets more token usage (more customer expansion), Warp gets more customers (I’m not paying $15/month, but I would plug in my key), and the user gets to have another LLM tool that otherwise they would not.
June 25, 2025
The Feedback Loop
I work at a pretty amazing company, Canva, that has a culture of feedback. I think I have given and received more feedback in the two years I’ve been here than in the rest of my professional life put together. It has taught me something critical about the importance of a growth mindset. Managing several teams also gave me a lot of perspective here.
I always thought a growth mindset would have an effect on what happens when you receive feedback, but now I’ve discovered it also has an effect on the frequency and complexity of the feedback. When I have a piece of feedback to give to someone, if that person has a growth mindset, I just give it.
For the people with a fixed mindset, I know I’ll have objections, challenges, push backs, defensiveness. In those cases, for the feedback to be accepted, I need to collect evidence. I may need one clear case to base my feedback around but then further ones to display the pattern. It takes a lot more work and effort, and at a time when my calendar is back to back meetings and my to do list keeps growing.
What naturally happens, even if I try to fight it, is that for people with a growth mindset, I give feedback frequently, and for people with a fixed mindset, I drop the frequency. A side effect of that is: the size of the feedback stays lower the higher the frequency is. This means the pain of receiving that feedback is lower. Let’s not pretend that receiving growth feedback is not painful.
I think a chart showing the two growth paths would help:
The lesson here is that gracefully accepting feedback has a massive impact on how much of it you will get, and if feedback is a source of growth, then it’s extra valuable to be graceful. Possibly even when the feedback is not correct: saying “Oh, interesting point, I’d like to think about it” is not that costly. This is a lesson I’m still learning.
April 19, 2023
The role of CTO should have no job description
I’ve been thinking about this one for a while. Imagine you are the CEO of a company and your competitors are getting ahead of you because they started to use ChatGPT to build their tech. You ask the CTO what’s going on and the CTO says “ChatGPT was not on my job description, so I ignored it”. How would you feel as the CEO? Would you think “Ok, fair enough for not putting it in the job description” or would your thoughts be a bit more… colorful?
The CEO has no job description other than: “Making the company successful“. The CEO is responsible for everything. If tech fails, it is the CEO’s fault, if accounting is done wrong, it is the CEO’s fault, if marketing wastes money, it is the CEO’s fault. The buck stops there for everything.
The CTO’s role is exactly that, but just the tech part. The job description should be “Do anything and everything in the tech domain to make the company successful“. Are the servers down? It is the CTO’s fault. Marketing doesn’t have the features they need for a campaign, it is the CTO’s fault. Ransomware destroys operations, it is the CTO’s fault. Tech is spending too much money, it is the CTO’s fault. Too much technical debt, too little, not delivering fast enough, having the wrong kind of skills on the devs, devs are unhappy. Again… it all falls on the CTO’s shoulders. At least this is how I take on the role when I carry the title of CTO.
This means the CTO is responsible for problems that nobody ever assigned to them. That’s one of the reasons this role is hard. And to be able to do such a role, the CTO needs autonomy, information, access, etc.
Autonomy is achieved through budget authority. The CTO presents a budget to the CEO and CFO, who approve it and then executes on it. Ideally, then the CTO receives periodic updates from the CFO comparing expenses to budgets, and whether the company has the revenue to back that budget up. If the CTO overspends beyond the tech budget, that’s a problem, but if the company shrinks, that’s a problem too. In both cases the CTO should be proactively thinking about how to cut cost and manage the expenses (before hitting a wall, having a massive layoff, etc).
Information and access is achieved through having a strong exec team. An exec team that is all on the same page. Including a clear vision from the CEO, a clear understanding on how all other departments are achieving their goals, and how tech helps or hinders them.
So if there is a job description at all, it should be not for the role of the CTO, but rather for the company itself: what it means to achieve, and how it behaves to empower its C-Suite to further their ambitions.
The post The role of CTO should have no job description first appeared on Pablo Fernandez.
February 12, 2023
Generating an all-files file in Windows, to verify backups
When I was 16 years old or so, one day, my computer didn’t boot. I got a blue screen with some white text-mode error. Something was broken with the file system. This was after being utterly disappointed by Windows 95 and before I became a Linux person, so I was running Windows NT 4.0. I was the only person I knew running that operating system, and thus the only person I knew with an NTFS partition.
What to do now? That was my only computer, thus I couldn’t get online, smartphones wouldn’t be invented for another decade, I had nobody to ask for help and no tools to run checks on an NTFS partition. That filesystem was quite new back then. I could just blank the hard drive and reinstall Windows NT and all the software. But what about my data? my data!!!
At 16 years old I learned the lesson that the data is the most valuable and important thing inside my computer. Everything is replaceable. I’m sure if I could see that data now I would life, but for 16-year-old-me, that was my life. I started making backups and since that day I had a personal backup strategy that’s more robust than 90% of the companies I talk to. I have yet to lose a file and I hope to keep it that way. Even my ex-wife recently recovered from a complete computer failure because she’s following the backup strategy I set up for her.
One of the things I wonder is, should I have to do a total restore of my data, how do I verify it? I have more than 2 million files. Big chunks could be missing and it might take me years to notice. Because I have so much data to backup, keeping my 3 backups all up to date is hard, so it’s possible that I may have to reconstruct my information piecing things together from the 3 of them. Technically my backup software should be able to do it. But… I’m skeptical.
This is why every night I have an automatic script that generates a list of all of my files in a text file. That text file gets backed up and unless that files gets permanently and historically lost, I can use it to verify a backup restore. I think my friend Daniel Magliola gave me this idea.
Since I use Windows (shocker, I know, but try building a Mac workstation with 6 screens and play video games and report back to me), I wrote the script in PowerShell, but since I couldn’t find anything like Linux’s find, the script invokes wsl. Here it is:
echo "Creating list of all files in C:"wsl find /mnt/c/Users/pupeno -type b,c,p,f,l,s > C:\Users\pupeno\.all-files.new.txtmove -Force C:\Users\pupeno\.all-files.new.txt C:\Users\pupeno\.all-files.txtecho "Creating lists of all files in D:"wsl find /mnt/d -type b,c,p,f,l,s > D:\.all-files.new.txtmove -Force D:\.all-files.new.txt D:\.all-files.txtecho "Creating lists of all files in E:"wsl find /mnt/e -type b,c,p,f,l,s > E:\.all-files.new.txtmove -Force E:\.all-files.new.txt E:\.all-files.txtAnd this is how it’s configured in the task scheduler to run every night:
I hope it helps.
The post Generating an all-files file in Windows, to verify backups first appeared on Pablo Fernandez.
February 7, 2023
The ultimate developer perk that’ll let you hire anyone you want
To convince people to come work for you you offer them, aside from compensation, perks. And you try to have better perks than your competitors (other employers). Brainstorming with my friend Justin a few years ago I came up with what I believe is the ultimate perk and since then I’ve been desperatly trying to find a place to deploy it. I haven’t found a place where I feel even comfortable bringing it up, that’s how far I am from deploying it, so I’m sharing it with the world. If I was running my own company, I’d deploy it in an instance, not giving it a second thought.
This is the perk: every developer gets a confidentiality-bound personal assistant. There wouldn’t be one PA per developer, because they don’t need nor have enough work that can be delegated to keep one person fully occupied. Instead there would be one for all of the developers (or two, or three… or whatever you need depending on how many devs you have).
First, I don’t think this would be a very expensive perk, because actually delegating work to a PA is a skill that you have to learn and most developers will not have it. Most developers will not know where to start. But most developers will likely enjoy saying “I have a PA” or “I have a secretary”. Having said that, I would actually run courses for the developers to learn to delegate because…
It’s a perk that pays for itself. You might think you are paying for extra employees or contractors, but your developers are likely the most expensive salaries in your books. So if they can pass a task to someone else, you are saving money, not spending money. Let’s say a developer needs to arrange a call with a vendor to discuss a technical mater. They have to search for the right person, their contact details, go back and forth over emails. Imagine if the developer just tells their PA “Can you arrange that call?” over a Slack message and that’s the end of it. Back to coding!
Think about making reservations, making doctors appointments, running errands, making phone calls in general (this is why they should be confidential). Developers tend to hate phone calls. Imagine if they could delegate to someone complaining about a flight ticket that got canceled! I once had a coworker who spent half a day on hold while pretending to code.
It’s a perk that generates retention: you know why Google pays for food, haircuts, laundry, doctors on-site, etc? It’s because that generates a lot of retention. When you quit Google, you not only need a job, you also need to find a hairdresser, a place to clean your clothes and make doctor’s appointments. They treat you like children and you became as dependent as you were on your parents. I’m not exactly sure where the ethical boundary is here, but offering a free PA feels on the good side of things.
It’s unique: nobody is doing and I bet nobody will start, even after it’s been proven successful. It’s like private offices: we have the studies to prove that developers need silence to focus and yet we cram them in open office buildings. Private offices, and PAs, are for the three-piece suit executives, not for the lowly developer, so it’s not a perk likely to get devalued when everybody picks it up, because nobody will.
It’s loud. Imagine when the developer is hanging out with other developers and jokingly says
Have your people call my people to arrange it
Oh… you don’t have people? I do… this email address and phone number is my PA, just call them… and if you want a PA, come work for us.
I’d love to try this some day…
The ultimate developer perk: you’ll hire anyone you want
To convince people to come work for you you offer them, aside from compensation, perks. And you try to have better perks than your competitors (other employers). Brainstorming with my friend Justin a few years ago I came up with what I believe is the ultimate perk and since then I’ve been desperatly trying to find a place to deploy it. I haven’t found a place where I feel even comfortable bringing it up, that’s how far I am from deploying it, so I’m sharing it with the world. If I was running my own company, I’d deploy it in an instance, not giving it a second thought.
This is the perk: every developer gets a confidentiality-bound personal assistant. There wouldn’t be one PA per developer, because they don’t need nor have enough work that can be delegated to keep one person fully occupied. Instead there would be one for all of the developers (or two, or three… or whatever you need depending on how many devs you have).
First, I don’t think this would be a very expensive perk, because actually delegating work to a PA is a skill that you have to learn and most developers will not have it. Most developers will not know where to start. But most developers will likely enjoy saying “I have a PA” or “I have a secretary”. Having said that, I would actually run courses for the developers to learn to delegate because…
It’s a perk that pays for itself. You might think you are paying for extra employees or contractors, but your developers are likely the most expensive salaries in your books. So if they can pass a task to someone else, you are saving money, not spending money. Let’s say a developer needs to arrange a call with a vendor to discuss a technical mater. They have to search for the right person, their contact details, go back and forth over emails. Imagine if the developer just tells their PA “Can you arrange that call?” over a Slack message and that’s the end of it. Back to coding!
Think about making reservations, making doctors appointments, running errands, making phone calls in general (this is why they should be confidential). Developers tend to hate phone calls. Imagine if they could delegate to someone complaining about a flight ticket that got canceled! I once had a coworker who spent half a day on hold while pretending to code.
It’s a perk that generates retention: you know why Google pays for food, haircuts, laundry, doctors on-site, etc? It’s because that generates a lot of retention. When you quit Google, you not only need a job, you also need to find a hairdresser, a place to clean your clothes and make doctor’s appointments. They treat you like children and you became as dependent as you were on your parents. I’m not exactly sure where the ethical boundary is here, but offering a free PA feels on the good side of things.
It’s unique: nobody is doing and I bet nobody will start, even after it’s been proven successful. It’s like private offices: we have the studies to prove that developers need silence to focus and yet we cram them in open office buildings. Private offices, and PAs, are for the three-piece suit executives, not for the lowly developer, so it’s not a perk likely to get devalued when everybody picks it up, because nobody will.
It’s loud. Imagine when the developer is hanging out with other developers and jokingly says
> “Have your people call my people to arrange it”
> “Oh… you don’t have people? I do… this email address and phone number is my PA, just call them… and if you want a PA, come work for us.”
I’d love to try this some day…
The post The ultimate developer perk: you’ll hire anyone you want first appeared on Pablo Fernandez.
February 3, 2023
You should not send rejection emails to job candidates when…
You should not send rejection emails to job candidates when there was no interaction. For example, in the case of rejecting someone just from the application, without a screening call.
There’s a mantra that good recruiters and hiring managers take on the difficult task of sending rejection emails instead of just ghosting candidates. But I don’t think this should be a black and white decision. Do you agree? Disagree? Please leave a comment with your point of view, I’m intrigued.
When you had a screening call with the candidate, then the candidate will likely be wondering about the next step, so if the rejection happens at this or any other later stage, then yes, ghosting is extremely rude and you should always send a rejection email and possibly some feedback for the candidate to improve. More on that later.
If there hasn’t been any interaction, you shouldn’t have your only interaction with the candidate be a rejection. The reason for this is that most of us have, at some point, been desperate, and started applying to lots of jobs hoping that someone would pay us some attention, hoping we may accidentally open a door, and because being homeless is worse that shotgunning job ads. And actually, when the job ads are anonymous bland indistinguishable walls of text, there’s not a lot you can do other than hit apply and move on, so don’t hold it against the candidate.
The problem is that then this candidate might have hundreds of applications that result in tens of rejections emails. Rejections for roles the candidate forgot about 10 seconds after hitting apply (how long can you remember a non-descript job post about an anonymous company anyway?), so all you are doing by sending the rejection is reminding the candidate that they didn’t get something they forgot they tried to get. When you get tens of these, one after another, it’s emotionally debilitating. It’s no wonder that a candidate might snap at one too many rejections.
Oh, and about feedback: if you have nothing to say, don’t say anything. If you are going to give feedback, give actionable feedback. Giving someone impossible feedback is a slap in the face. For example, for a few years, I’ve been wanting to have an engineer manager position at a big scale-up or at a big company. When I get rejected and I ask for feedback, most of the time it boils down to “You haven’t been an engineer manager at a big company before”. What am I going to do with that? They might as well tell me “Have you tried being a different person? Have you tried having been born in a different country?” It’s useless and infuriating.
The post You should not send rejection emails to job candidates when… first appeared on Pablo Fernandez.
December 10, 2022
How to solve CS-705 error “Connected transceiver is not compatible model”
I just got an Icom IC-750 with the goal of going out in the open and the first thing I did is try to configure it with my computer because it’s what’s most convenient for me. I like keeping backups of various configurations and being able to go back to them. I’m also working on a project to help set up repeaters in this and others radios.
Unfortunately, I encountered the error:
Connected transceiver is not compatible model.
Check the following:
Appropriate programming software for the transceiver is being used.The revision number of the transceiver
What was confusing is that the CS-705 was correctly seeing my connected IC-705:
I tried a few things, but long story short, my IC-705 had firmware version 1.31 and I was running CS-705 version 1.11 when only version 1.20 supports firmware 1.31. Now that I upgraded everything works:
And for the record, I’m running the USB driver version 1.12.
I find the Icom’s website to download the different versions of the software a bit confusing (especially the interactions between Icom UK and Icom Japan), but you can find it here: https://www.icomjapan.com/support/firmware_driver/?keyword=705&open=tab2&type=5#download_result
The post How to solve CS-705 error “Connected transceiver is not compatible model” first appeared on Pablo Fernandez.
October 21, 2022
Building my green French cleat wall
If you follow any of these links to buy a product, I get a commission that goes towards making more videos.
Tools used Drill:US: possibly a similar one: https://amzn.to/3TlhRB0UK: Ryobi R18PD5: https://amzn.to/3TWIs7xSDS Drill (masonry):US: I think it’s the Ryobi P222: https://amzn.to/3MSYgG2UK: Ryobi R18SDS-0: https://amzn.to/3VWzqcqNailgun:US: I think it’s the Ryobi P320: https://amzn.to/3SfruQdUK: Ryobi R18N18G: https://amzn.to/3MOgkAOMakita Plunge Saw:US: https://amzn.to/3THMHTZUK: https://amzn.to/3eN31nJMakita SDS Drill Bits:US: couldn’t find itUK: https://amzn.to/3gfuCyeFestool TKS 80 Table SawUS: not availableUK: https://amzn.to/3yTMo0DKreg Pocket Hole Jig:US: https://amzn.to/3DilSAJUK: https://amzn.to/3Dg6IvLMicrojig Grr-ripperUS: https://amzn.to/3VO6CCTUK: https://amzn.to/3MOa6RwVideo productionCamera: GoPro Hero 9: US: https://amzn.to/3sfseKQUK: https://amzn.to/3sg6CxYLights: Apurture MC 4-Light Travel KitUS: https://amzn.to/3yZfsUjUK: https://amzn.to/3Dg6DYTMicrophone: Tascam DR-10L/LW:US: https://amzn.to/3gnXIM0UK: https://amzn.to/3VLfgBPThe post Building my green French cleat wall first appeared on Pablo Fernandez.
July 31, 2022
Creating blog posts in WordPress.com programatically
One of my projects, Unbreach, has a database of more than 600 breaches. These come from haveibeenpwned and they are composed of some metadata, a one-paragraph description, and an image. I wanted to improve these with more content, links to articles, tweets, videos, and some content of my own.
I decided that a good way to do it would be to move them from the app, which resides at app.unbrea.ch, to the marketing website, which is at unbrea.ch, essentially creating them as blog posts. That way after the blog post is automatically created (when haveibeenpwned ads the breach), I can go in and manually edit it in all the WordPress glory. I thought this was going to take me a few hours, not days.
Hopefully, with this blog post, it’ll only take you hours. I’ll be using Ruby but it should be trivial to translate it to Python, JavaScript, or any other programming language. Writing the code wasn’t the hard part, understanding the WordPress.com world was.
WordPress has two different APIs that should be able to accomplish this task, one is the XML-RPC API and the other is the REST API. The XML-RCP API depends on a file called xmlrpc.php and it’s strongly recommended you leave this deactivated because it had a lot of security issues. It’s also old, cumbersome, and possibly on the way out. I didn’t want to use it and I don’t think you should either.
From what I can gather the REST API is what the admin tool uses, so using it sounds like a safe bet. If you are going to be creating blog posts from an unattended background process, as I do, you’ll find your first obstacle when you read about authentication because it just assumes there’s a browser sending cookies.
Fear not! There are plug-ins that implement other authentication methods and one of those is the Application Passwords plug-in. Which is now discontinued because it’s been merged into WordPress itself in version 5.6. This sounds promising until you realize the feature seems to be missing in WordPress.com.
If you search how to create an Application Password on WordPress.com you’ll land in the wrong place. WordPress.com users have an Application Password that’s hidden behind the Two-Step Authentication in Security. This is what it looks like:
If you are here you are in the wrong placeWhat’s going on here? Well, WordPress.com has its own API, which is a REST API, and if you talk to support and WordPress.com they’ll point you to that. I wasn’t a fan of that solution because although I want to use WordPress.com, I don’t want to be tied to it. I want to be able to move to WP Engine or something like that whenever I want.
That API, similar to the REST API, assumes there’s a human interacting through a third-party application, so it’s not great for unattended processes. Authentication works using OAuth2 which for a background job that just needs an API key I find very annoying. It’s doable but annoying. Well… it’s doable until you enable 2FA and then it’s not doable anymore, and that’s why that specific Application Password exists.
WordPress.com support also told me that the WordPress REST API is enabled only if you are on a business plan or above.
So… where’s the Application Password for the REST API then? I don’t know if there’s a link to it anywhere, but you get to it by going to https://example.com/wp-admin/profile.php where example.com is the URL of your blog. That is, add /wp-admin/profile.php to it. On WordPress.com’s defense, it was their support that finally pointed me to it. When you go there you’ll see an old-style profile page:
The correct place to set up an application password to use the WordPress REST APIThe previous Application Password was tied to the user, this one is tied to the user and the site, so if you have more than one site you’ll need to create one per site.
And that was the hard part. Once I got that application password things just worked. It’s a straightforward and mostly well-documented API. I’ll share my messy code here anyway (sorry, didn’t have time to clean it up).
In Ruby I’m using a library called Faraday to talk to APIs. The first thing is creating the Farady object that has the metadata that will be used in all the requests:
auth_token = "#{Rails.application.credentials.wordpress&.username}:#{Rails.application.credentials.wordpress&.app_pass}"auth_token = Base64.strict_encode64(auth_token)conn = Faraday.new(url: ENV["WORDPRESS_URL"], headers: { "Authorization" => "Basic #{auth_token}" }) do |conn| conn.request :json conn.response :jsonendAccording to Faraday’s documentation, this should have worked as a better way of setting up the authentication details:
conn.request :authorization, :basic, Rails.application.credentials.wordpress&.username, Rails.application.credentials.wordpress&.app_passbut for me it didn’t. It was completely ignored. About those two values, Rails.application.credentials.wordpress&.username is the username of the user that will be creating the posts and Rails.application.credentials.wordpress&.app_pass is the corresponding application password. ENV["WORDPRESS_URL"] is the URL of the WordPress site, like https://unbrea.ch/.
The first thing I need is the id of the category in which these posts will end up. This is very important because they appear on a separate page about breaches and not on the blog and that’s achieved with categories:
response = conn.get("/wp-json/wp/v2/categories", {search: "Breach", _fields: %w[id name]})if response.status != 200 raise "Unexpected response #{response.status}: #{response.body}"endcategory = response.body.find { |category| category["name"] == "Breach" }Now, if the category doesn’t exist, I want to create it:
if category.nil? response = conn.post("/wp-json/wp/v2/categories") do |req| req.body = {name: "Breach"} end if response.status != 201 raise "Unexpected response #{response.status}: #{response.body}" end category = response.bodyendThen I needed to do the same with tags. In my case, the tags were in a field called data_classes and the code for getting the id of the tag and creating it if it doesn’t exist is very similar:
tags = data_classes.map do |data_class| response = conn.get("/wp-json/wp/v2/tags", {search: data_class, _fields: %w[id name]}) if response.status != 200 raise "Unexpected response #{response.status}: #{response.body}" end tag = response.body.find { |tag| tag["name"] == data_class } if tag.nil? response = conn.post("/wp-json/wp/v2/tags") do |req| req.body = {name: data_class} end if response.status != 201 raise "Unexpected response #{response.status}: #{response.body}" end tag = response.body end tagendAnd finally, we can create the post. I create the content as an HTML snippet which causes WordPress to interpret it as classic content, not as blocks. But that’s fine because it renders well and the first time I edit one of those posts converting them to blocks is two clicks and works perfectly for this simple content.
content = <#{description}Accounts breached: #{pwn_count}
Breached on: #{breach_date&.strftime("%B %d, %Y")}
Exposed data: #{data_classes.to_sentence}
Domain: #{domain}
Added on: #{added_date.strftime("%B %d, %Y")}
CONTENTresponse = conn.post("/wp-json/wp/v2/posts", { title: title, content: content, excerpt: description, status: "publish", categories: [category["id"]], tags: tags.map { |tag| tag["id"] }, date_gmt: (breach_date.to_time(:utc) 12.hours).iso8601.to_s, template: "breach-template", ping_status: "closed"})if response.status != 201 raise "Unexpected response #{response.status}: #{response.body}"endpost = response.bodyAt this point, I wasn’t done. I wanted these posts to have the image associated with the breach (the logo of the company breached). The first step was downloading it which was a trivial one-liner:
logo_request = Faraday.new(url: logo_path).get("")In that code, logo_path is actually a full URL of the file.
To create media items in WordPress, I needed to encode the post as multi-part, so I ended up creating a separate Faraday object for that:
multipart_conn = Faraday.new(url: ENV["WORDPRESS_URL"], headers: {"Authorization" => "Basic #{auth_token}"}) do |conn| conn.request :multipart conn.response :jsonendIt should have been possible to use a single Faraday object for all requests, but when you specify multipart, you need to take care of encoding the JSON requests yourself and adding them as one of the parts. This is where I got lazy and just moved on with my work.
The code for creating the image in WordPress is this:
extension = File.extname(logo_path)file_name = "#{name.underscore.tr("_", "-")}#{extension}"content_type = if extension == ".png" "image/png"else raise "Unexpected extension #{extension}"endmedia = multipart_conn.post("/wp-json/wp/v2/media", { date_gmt: (breach_date.to_time(:utc) 12.hours).iso8601.to_s, status: "publish", title: title, comment_status: "closed", ping_status: "closed", alt_text: "Logo for #{title}", caption: "Logo for #{title}", description: "Logo for #{title}", post: post["id"], file: Faraday::Multipart::FilePart.new(StringIO.new(logo_request.body), content_type, file_name)})In reality, 100% of the images are PNG so I was ok with such a simplistic approach. When creating the FilePart I wrapped logo_request.body in a StringIO because it already contained the binary data of the image. If you have a local file you can just pass the path to FilePart.new and it just works.
And now that I had the image, I could set it as the featured image for the post I created earlier:
response = conn.post("/wp-json/wp/v2/posts/#{post["id"]}", { featured_media: media.body["id"]})if response.status != 200 raise "Unexpected response #{response.status}: #{response.body}"endThe reason why I didn’t create the image before creating the post was so that I could pass the post id to the image and thus the image would be connected to the post. I’m not sure how useful that is.
And that’s all.
I wonder if this code should be put in a gem and made reusable. WordPress points to the wp-api-client gem as the Ruby solution, which is read-only and abandoned. There’s also wordpress_v2_api, but I wasn’t a fan of the API (it’s almost like using HTTP directly), it hasn’t been touched in 6 years and I don’t believe it supports writing. I’m half tempted to fork wp-api-client, but does anybody else care, or is it just me? Please leave a comment if this is something you want to use.
The post Creating blog posts in WordPress.com programatically first appeared on Pablo Fernandez.


