Comments & reactions
Both endpoints are public reads, keyed on a project plus a content slug — the same slug the blog, docs, or changelog endpoints use. Neither is in the OpenAPI spec yet; they're documented here.
These endpoints are read-only. Posting a comment and toggling a reaction go through
gated Server Actions with reader authentication, not through /api/v1. There is no way
to write a comment with an API key.
Comment thread
GET /api/v1/comments?projectId=…&slug=…
Both parameters are required; omitting either returns 400.
curl "https://api.inkform.dev/api/v1/comments?projectId=YOUR_PROJECT_ID&slug=hello-world"{
"data": [
{
"id": "c1",
"body": "Great post.",
"authorName": "Ada",
"createdAt": "2026-09-02T09:14:00.000Z",
"replies": [
{
"id": "c2",
"body": "Agreed.",
"authorName": null,
"createdAt": "2026-09-02T10:01:00.000Z",
"replies": []
}
]
}
]
}Notes worth planning for:
- The tree is already nested and unbounded in depth — render it recursively.
- Ordering is oldest-first at every level.
- Deleted comments are omitted entirely, along with their replies.
authorNamecan benull; fall back to something like "Anonymous" in your UI.- An unknown slug returns
200withdata: [], not404.
Comment bodies are user-submitted text. Escape them when rendering — never inject one as HTML.
Reaction counts
GET /api/v1/reactions?projectId=…&slug=…
curl "https://api.inkform.dev/api/v1/reactions?projectId=YOUR_PROJECT_ID&slug=hello-world"{ "data": { "love": 12, "like": 30, "total": 42 } }Two reaction types, love and like, plus their sum. An item nobody has reacted to
returns zeroes, not 404.
Rendering both on a post page
One fetch each, in parallel, at request time:
const BASE = 'https://api.inkform.dev/api/v1';
async function getSocial(projectId: string, slug: string) {
const q = `projectId=${projectId}&slug=${encodeURIComponent(slug)}`;
const [comments, reactions] = await Promise.all([
fetch(`${BASE}/comments?${q}`, { next: { revalidate: 60 } }).then((r) => r.json()),
fetch(`${BASE}/reactions?${q}`, { next: { revalidate: 60 } }).then((r) => r.json()),
]);
return { comments: comments.data, reactions: reactions.data };
}Unlike published content, these change between publishes — a short revalidate window suits them better than build-time fetching.