Companion thread API
Open a post's companion discussion thread with one authenticated call, from whatever tooling you already publish with. This is the automation behind "a comment section for your blog, without running a comment section": your blog stays your blog, and each post gets a discussion thread on PopsicleBoat that you can link to and pull replies from.
You don't need this API to use PopsicleBoat as your comments — you can create threads by hand in the web UI. This just removes the copy-paste for people who publish often.
1. Get set up (once)
-
Have an account and a space. Claim your space at popsicleboat.com/for-bloggers (or ask me, through the letters page, to set it up). A thread is a post in a space.
-
Mint a personal access token. Sign in, open your Settings page, and follow the API tokens link. Mint the token yourself — it is shown once, to you alone; only a hash is stored, so nobody (including me) can see it again. Store it where your publishing tool runs, e.g.
export POPSICLEBOAT_TOKEN=pbt_xxxxxxxxRevoke a token from the same page any time; tokens don't expire on their own.
2. Create a thread
POST https://www.popsicleboat.com/api/companion-threads
Authorization: Bearer <POPSICLEBOAT_TOKEN>
Content-Type: application/json
{
"space": "blogging",
"title": "Discuss: The page that rebuilds itself",
"body": "Companion thread for [my post](https://jva.lol/weblog/the-page-that-rebuilds-itself/).",
"source": "https://jva.lol/weblog/the-page-that-rebuilds-itself/"
}
| Field | Required | Notes |
|---|---|---|
space |
yes | The space's name, matched case-insensitively (the /c/<space> slug). |
title |
yes | The thread title. |
body |
yes | Markdown. Include a real link back to your post — that's the loop. |
source |
no | The blog post this thread discusses. Becomes the thread's source link: it drives the "Discussing a post on …" meta line, and in build-hook spaces the composer tells readers their replies show up on the post. Must be a public http(s) URL — a bad one fails the request. |
The thread is created as your user, public, in the named space.
Success — 201 Created
{
"url": "https://www.popsicleboat.com/c/blogging/posts/260",
"id": 260,
"space": "blogging"
}
Put url wherever your post records its discussion thread (for the Hugo
reference client, that's the post's popsicleboat: front-matter field).
Errors
| Status | error |
When |
|---|---|---|
401 |
unauthorized |
Missing or unknown Bearer token. |
404 |
space_not_found |
No approved space by that name — claim it first. |
422 |
missing_field |
space, title, or body was blank/absent. |
422 |
invalid |
The post was rejected (e.g. posting velocity, or a source that isn't a public http(s) URL). |
Every error body is { "error": "<code>", "message": "<human sentence>" }.
Example
curl -sS https://www.popsicleboat.com/api/companion-threads \
-H "Authorization: Bearer $POPSICLEBOAT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"space":"blogging","title":"Discuss: my post","body":"Companion to [my post](https://example.com/my-post/)."}'
Security model
- Per-user, hashed. A token identifies one user. Only its SHA-256 hash is stored; the plaintext exists only in your hands.
- Scope. A token can do exactly what this endpoint does: create a public thread in an approved space, as you — the same thing you could do by hand in the web UI. It is not a session and grants no other access.
- Revoke. Revoke any token yourself from the same Settings page that minted it. Mint a fresh one anytime; they don't expire on their own.
- Treat it like a password. Keep it out of your repo and your build logs; pass it through an environment variable.
Reading replies back
Each thread's public replies live at the thread's address plus
/replies.json (or by post URL: /discuss/replies.json?url=...). A reply
deleted while a conversation still stands underneath syndicates as a stub:
"deleted": true with the public removal line as its body, nothing else.
Render it like any reply, or filter on the flag if your site prefers absence.
Reference client
The Hugo blog at jva.lol drives this with
scripts/companion-thread — it takes a post, calls this
endpoint, and writes the returned url into the post's popsicleboat: field,
so publishing a post with its thread is one command.