API
All responses are JSON. Errors share one shape:
GET /api/comments
- Missing file returns
200with 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
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
Verifies only. Returns post, name, content, parentId, createdAt, hasEmail, exp.
Errors: 400 token_invalid, 400 token_expired
approve
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.
Incomplete configuration returns 503 with status: "degraded".
