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 Popsicle Boat that you can link to and pull replies from.

You don't need this API to use Popsicle Boat 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)

  1. 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.

  2. 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_xxxxxxxx
    

    Revoke 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.

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.