I gave my AI assistant API credentials to my Drupal site today and told it to publish a post. Not a thought experiment. Real Basic Auth, real JSON:API, a real publish transition fired by something that is not me.
The goal was simple: shake down the content endpoints on austincodez.dev before building anything serious on top of them. Reads, drafts, publishing, deletes. What I got was a working pipeline plus a short list of sharp edges worth writing down.
The boring part worked
All five content types returned clean 200s on the first try: blog, news, pages, projects, coffee reviews. Creating a draft worked too, once I learned the local customs:
- You cannot set the
statusfield directly on moderated entities. Drupal answers with a 403 and tells you the moderation state drives it. Fair enough. - The body field wanted just a
valuekey. Passing a text format got a 422. Omit it and the field default applies. - The API user needed the draft-to-published transition permission granted explicitly before it could publish. Obvious in hindsight, easy to forget.
The interesting part broke
Deleting is forbidden for the API user, which is fine, that is a permission I chose. The real surprise was the unpublish that lied to me.
After publishing the test post, I sent a PATCH to move it back to draft. The API responded with the node showing draft status. Everything looked right. Except the post was still live. Anonymous visitors got a 200 on the full page while the API insisted it was a draft.
The cause: the node had a working copy, and Drupal core does not support updating such resources through JSON:API at all (there is an open core issue about it, #2795279). The PATCH created a draft working copy while the published revision sat there untouched, and the response I read back described the working copy, not what the world sees.
Lesson learned the fun way: after any write, verify from the outside. Curl the page as an anonymous visitor. Trust that, not the API response.
One more: timeouts can duplicate
My first POST timed out after 20 seconds with no usable response. The retry succeeded in under a second. Both created a node. The timed-out request had gone through just fine; only the response was lost. If your client retries writes blindly, you get twins. Idempotency keys exist for a reason, and JSON:API does not hand you one.
So, would I do it again?
Yes. The whole round trip took minutes, and every failure taught me something about my own site's configuration rather than the API itself. If you are wiring up editorial automation against Drupal's JSON:API, my checklist is short:
- Grant transition permissions deliberately, per role.
- Drive publish state through moderation state, never the status field.
- Verify every write anonymously, not just through the API response.
- Assume a timed-out write may have succeeded. Check before retrying.
The test posts are deleted, the site is clean, and the endpoints are ready for whatever I build next.