API Reference
Kirby Loop provides a RESTful API for managing comments and feedback. All endpoints include CSRF protection.
Authentication
All API endpoints require authentication, controlled by the moinframe.loop.public
configuration option:
- Default (private): Only authenticated Kirby users can access the API
- Public mode: Anyone can access the API
CSRF Protection
All API requests must include a valid CSRF token in the request header:
fetch('/loop/comments/page-id', {
headers: {
'X-CSRF-Token': '<csrf-token>'
}
});
Base URL Structure
Single Language Sites
/loop/comments/{pageId}
/loop/comment/new
/loop/comment/reply
/loop/comment/resolve
/loop/comment/unresolve
/loop/guest/name
Multi-Language Sites
/{language}/loop/comments/{pageId}
/{language}/loop/comment/new
/{language}/loop/comment/reply
/{language}/loop/comment/resolve
/{language}/loop/comment/unresolve
/{language}/loop/guest/name
Where {language}
is the language code (e.g., en
, de
).
Endpoints
GET /loop/comments/{pageId}
Retrieve all comments for a specific page.
Parameters:
pageId
(string): The page ID or 'home' for the homepage
Response:
{
"status": "ok",
"comments": [
{
"id": 1,
"author": "John Doe",
"url": "https://example.com/page",
"page": "page-uuid",
"comment": "This needs to be updated",
"selector": ".header h1",
"selectorOffsetX": 10,
"selectorOffsetY": 20,
"pagePositionX": 150,
"pagePositionY": 300,
"timestamp": 1640995200,
"lang": "en",
"status": "OPEN",
"replies": [
{
"id": 1,
"author": "jane.smith",
"comment": "I'll fix this",
"parentId": 1,
"timestamp": 1640995800
}
]
}
]
}
Error Responses:
400
: Page not found401
: Unauthorized (if authentication required)403
: CSRF token invalid
POST /loop/comment/new
Create a new comment on a page.
Request Body:
{
"comment": "This section needs clarification",
"url": "https://example.com/page",
"selector": ".content p:nth-child(3)",
"selectorOffsetX": 15,
"selectorOffsetY": 25,
"pagePositionX": 200,
"pagePositionY": 450,
"pageId": "projects/project-alpha"
}
Required Fields:
comment
(string): The comment text (HTML stripped and sanitized)url
(string): The full URL where the comment was madeselector
(string): CSS selector for the commented elementselectorOffsetX
(number): X offset within the selected elementselectorOffsetY
(number): Y offset within the selected elementpagePositionX
(number): X position on the pagepagePositionY
(number): Y position on the pagepageId
(string): Kirby page ID or 'home'
Response:
{
"status": "ok",
"comment": {
"id": 15,
"author": "John Doe",
"url": "https://example.com/page",
"page": "page-uuid",
"comment": "This section needs clarification",
"selector": ".content p:nth-child(3)",
"selectorOffsetX": 15,
"selectorOffsetY": 25,
"pagePositionX": 200,
"pagePositionY": 450,
"timestamp": 1640995200,
"lang": "en",
"status": "OPEN",
"replies": []
}
}
Error Responses:
400
: Missing required fields, invalid selector format, or invalid data401
: Unauthorized403
: CSRF token invalid or disabled404
: Page not found
POST /loop/comment/reply
Add a reply to an existing comment.
Request Body:
{
"comment": "I'll handle this update",
"parentId": 15
}
Required Fields:
comment
(string): The reply text (HTML stripped and sanitized)parentId
(number): ID of the parent comment
Response:
{
"status": "ok",
"reply": {
"id": 3,
"author": "John Doe",
"comment": "I'll handle this update",
"parentId": 15,
"timestamp": 1640995800
}
}
Error Responses:
400
: Missing required fields401
: Unauthorized403
: CSRF token invalid or disabled
POST /loop/comment/resolve
Mark a comment as resolved.
Request Body:
{
"id": 15
}
Required Fields:
id
(number): The comment ID to resolve
Response:
{
"status": "ok",
"success": true
}
Error Responses:
400
: Missing comment ID401
: Unauthorized403
: CSRF token invalid or disabled
POST /loop/comment/unresolve
Mark a resolved comment as unresolved.
Request Body:
{
"id": 15
}
Required Fields:
id
(number): The comment ID to unresolve
Response:
{
"status": "ok",
"success": true
}
Error Responses:
400
: Missing comment ID401
: Unauthorized403
: CSRF token invalid or disabled
POST /loop/guest/name
Set a guest name for non-authenticated users (when public mode is enabled).
Request Body:
{
"name": "John Doe"
}
Required Fields:
name
(string): The guest user's name
Response:
{
"status": "ok",
"name": "John Doe"
}
Error Responses:
400
: Missing or empty name401
: Unauthorized403
: CSRF token invalid or disabled
Data Models
Comment Object
interface Comment {
id: number;
author: string; // Resolved display name (user name, email prefix, or guest name)
url: string; // Full URL where comment was made
page: string; // Page UUID
comment: string; // Sanitized comment text
selector: string; // CSS selector for target element
selectorOffsetX: number; // X offset within element (float)
selectorOffsetY: number; // Y offset within element (float)
pagePositionX: number; // X position on page (float)
pagePositionY: number; // Y position on page (float)
timestamp: number; // Unix timestamp
lang: string; // Language code
status: string; // Status: OPEN, RESOLVED
replies: Reply[]; // Array of replies
}
Reply Object
interface Reply {
id: number;
author: string; // Resolved display name (user name, email prefix, or guest name)
comment: string; // Sanitized reply text
parentId: number; // Parent comment ID
timestamp: number; // Unix timestamp
}
Error Handling
The api endpoints return consistent error responses. For more details, switch on the debug mode in your Kirby Installation.
{
"status": "error",
"message": "Human-readable error message",
"code": "ERROR_CODE" // Optional error code
}
Common Error Codes
CSRF_INVALID
: CSRF token is missing or invalidPAGE_NOT_FOUND
: Specified page doesn't existFIELD_REQUIRED
: Required field is missingUNAUTHORIZED
: Authentication required but not providedINVALID_SELECTOR
: Invalid selector formatINVALID_NAME
: Invalid guest nameDISABLED
: Tool is disabled