# Claude can now write my blog posts

Until today, every article on this site was typed by hand directly into Cosmos DB via Data Explorer. That's changed — I built a proper write API so Claude can create and update posts on its own.

The original plan was to build an admin UI for this — a form, a login screen, the usual. Maybe I'll still get to that someday. But halfway through, it hit me: why build a UI when I can just have Claude write the article directly? Faster to ship, and honestly way cooler.

## What changed

- Added `POST /articles` and `PUT /articles/{slug}` to the blog service API.
- Auth is a dedicated Azure AD app registration using the client-credentials flow — an app-only token, no interactive login. Authorization checks a custom `Articles.Write` app role on the token, not just "any valid token," so a narrower delete-only role can be added later without a redesign.
- Requests are validated (title/slug/content required) and rate-limited separately from the public read endpoints.
- Slugs are generated by Claude before calling the API; a duplicate slug gets a `409` instead of silently overwriting anything.

## The bug that came with it

Shipping this also uncovered a real production bug on day one: the first article created through the new write path broke reads for the *entire* blog. Every hand-written article has a legacy numeric `Id` field that the new write path deliberately doesn't set — and it turned out Cosmos's serializer throws, rather than quietly defaulting, when that field is missing. One new article was enough to 500 every `GET /articles` call. Fixed by having every read go through the same tolerant document type the write path already used, instead of binding straight into the API's public article model. Found and fixed live, same day.

This article is the proof — it was created and then updated through the very API described above, not typed into Cosmos by hand.

---

*Co-authored with Claude.*

If this was useful, [you can buy me a coffee](https://ko-fi.com/p47k0).

New articles: [follow via RSS](https://blog.koorevaar.com/feed.xml).
