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 /articlesandPUT /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.Writeapp 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
409instead 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.