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:

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:

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:

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:

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:

POST /loop/comment/reply

Add a reply to an existing comment.

Request Body:

{
    "comment": "I'll handle this update",
    "parentId": 15
}

Required Fields:

Response:

{
    "status": "ok",
    "reply": {
        "id": 3,
        "author": "John Doe",
        "comment": "I'll handle this update",
        "parentId": 15,
        "timestamp": 1640995800
    }
}

Error Responses:

POST /loop/comment/resolve

Mark a comment as resolved.

Request Body:

{
    "id": 15
}

Required Fields:

Response:

{
    "status": "ok",
    "success": true
}

Error Responses:

POST /loop/comment/unresolve

Mark a resolved comment as unresolved.

Request Body:

{
    "id": 15
}

Required Fields:

Response:

{
    "status": "ok",
    "success": true
}

Error Responses:

POST /loop/guest/name

Set a guest name for non-authenticated users (when public mode is enabled).

Request Body:

{
    "name": "John Doe"
}

Required Fields:

Response:

{
    "status": "ok",
    "name": "John Doe"
}

Error Responses:

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