If you script your posting to dev.to, the obvious way to confirm a post went live is to read published back from the API. On the endpoint you would naturally use for that, the field is not false and it is not null. It is not in the response at all, so article.get("published") hands you None and your script decides a live article is still a draft.
The reliable check is simpler: use an unauthenticated GET /api/articles/{id} returning HTTP 200 as proof that a stranger can read the article. Use the author-scoped collections when you need to distinguish drafts from published posts.
Everything below was measured on 2026-08-21 against dev.to with API v1, on my own account, with three published articles and one draft.
One article, two shapes
The author-scoped list endpoint carries the field:
curl -s "https://dev.to/api/articles/me/published?per_page=1" \
-H "api-key: $DEVTO_KEY" \
-H "accept: application/vnd.forem.api-v1+json" \
| python3 -c 'import sys,json;a=json.load(sys.stdin)[0];print(a["id"], a["published"], a["published_at"])'
4408696 True 2026-08-16T07:13:13.534Z
The single-article endpoint, same key, same article id, does not:
curl -s "https://dev.to/api/articles/4408696" \
-H "api-key: $DEVTO_KEY" \
-H "accept: application/vnd.forem.api-v1+json" \
| python3 -c 'import sys,json;a=json.load(sys.stdin);print("published" in a, a.get("published_at"))'
False 2026-08-16T07:13:13Z
I ran that request three ways, with the api-key header, without it, and without the vendor accept header, and "published" in a was False every time. So this is not a permissions difference between an author and a stranger. The key does not change it.
The write path is the same shape. A PUT /api/articles/{id} returned HTTP 200 and a body with no published key either, even though published is a field I had just sent in the request.
Which leaves the two ways a script gets this wrong. article["published"] raises KeyError on a successful publish, and article.get("published") quietly reports a public article as unpublished, which is worse, because that branch usually retries.
Why
published is an input. You send it on POST /api/articles and on PUT /api/articles/{id} to move a draft to public. The collections under /api/articles/me/* are author-scoped, so they describe your article the way its owner sees it and echo the flag back.
GET /api/articles/{id} is a different representation. It serves the article the way an anonymous reader receives it, and in that view there is no publication state to report, because an article you can fetch there is by definition one that is published.
The negative control
That last sentence is the part worth testing rather than assuming, so I asked the endpoint for something the public cannot see. I created a draft with "published": false, took its id from GET /api/articles/me/unpublished, and requested it directly.
curl -s -o /dev/null -w "%{http_code}\n" "https://dev.to/api/articles/4449977"
404
Then the same request with my own key, for my own draft:
curl -s -w " HTTP %{http_code}\n" "https://dev.to/api/articles/4449977" \
-H "api-key: $DEVTO_KEY" \
-H "accept: application/vnd.forem.api-v1+json"
{"error":"not found","status":404} HTTP 404
My draft, my key, still 404. The endpoint has nothing to say about anything that is not public, which is why the flag is missing rather than false.
Check the thing you actually care about
The 404 is the useful half. It turns the presence of a response into the signal:
code=$(curl -s -o /dev/null -w "%{http_code}" "https://dev.to/api/articles/$ID")
[ "$code" = "200" ] || { echo "not public: HTTP $code"; exit 1; }
An unauthenticated 200 means a stranger can read the article. That is the property you were after when you went looking for published, and it is a stronger check than a boolean echoed back from your own request, because nothing about it depends on your credentials. Take the timestamp from published_at on that same response.
If you need the other direction, the list of what is still a draft, ask the author-scoped endpoint. GET /api/articles/me/unpublished returned published: false for my draft and me/published returns true. Those endpoints answer the ownership question. The id endpoint does not, and the missing key is the only notice you get.
Where this was tested
dev.to itself, API v1, with the accept: application/vnd.forem.api-v1+json header, on 2026-08-21. I did not test a self-hosted Forem instance, an organization article, or a scheduled post, and a self-hosted instance can be several versions behind. If the field starts appearing on the id endpoint later, the unauthenticated 200 check keeps working regardless, which is why my own publishing script gates on that instead of on the flag.
If you run the same check against a self-hosted Forem or a scheduled post, I would be interested in the response shape and Forem version. Those are the two cases I have not tested.
Top comments (0)