API

All responses are JSON. Errors share one shape:

{ "error": "invalid_post", "message": "post is invalid" }

GET /api/comments

GET /api/comments?post=hello-world
{
  "post": "hello-world",
  "updatedAt": "2026-10-30T05:00:00.000Z",
  "comments": [
    { "id": "...", "parentId": null, "name": "Alice", "content": "Nice.", "createdAt": "2026-10-30T04:12:00.000Z" }
  ]
}
  • Missing file returns 200 with an empty array
  • Email fields are never present
  • Cache-Control: public, s-maxage=60, stale-while-revalidate=600
  • CORS only for origins in ALLOWED_ORIGINS

Errors: 400 invalid_post, 403 origin_not_allowed, 502 git_unavailable, 503 service_unavailable

POST /api/submissions

The body must contain action.

challenge

{ "action": "challenge", "post": "hello-world", "name": "Alice", "content": "Nice.", "parentId": null }

Returns { "ok": true, "siteKey": "...", "turnstileAction": "submit", "cData": "..." }.

submit

Challenge fields plus turnstileToken, optional email, and the honeypot field website which must be empty.

With moderation: { "ok": true, "pending": true } Without moderation: { "ok": true, "pending": false, "id": "..." }

Errors: 400 turnstile_failed, 400 invalid_submission, 400 spam_rejected, 403 origin_not_allowed, 429 rate_limited, 502 mail_send_failed, 502 git_write_failed, 503 mail_not_configured

preview

{ "action": "preview", "token": "<envelope>" }

Verifies only. Returns post, name, content, parentId, createdAt, hasEmail, exp.

Errors: 400 token_invalid, 400 token_expired

approve

{ "action": "approve", "token": "<envelope>" }

Returns { "ok": true, "id": "...", "created": true }. created: false means an idempotent hit.

Errors: 400 token_invalid, 400 token_expired, 403 origin_not_allowed, 409 conflict, 502 git_write_failed

GET /api/health

No auth, no secrets.

{
  "status": "ok",
  "git": { "repo": "owner/repo", "branch": "main", "path": "comments", "readable": true, "writable": true },
  "sameRepo": false,
  "sameRepoWarning": null,
  "moderation": true,
  "moderationWarning": null,
  "mail": { "provider": "resend", "configured": true },
  "issues": [],
  "timestamp": "2026-10-30T05:00:00.000Z"
}

Incomplete configuration returns 503 with status: "degraded".