This document describes the HTTP endpoints currently exposed by the backend.
- Base URL:
http://localhost:8080 - Data format: JSON unless otherwise specified
- Authentication: Spring Security session cookie (
JSESSIONID) - Default verification code length:
6 - Default upload limit:
10 MB - Chinese version: API_zh.md
After a successful login, the server maintains authentication through the JSESSIONID cookie. Requests to /u/** require the USER or ADMIN authority, while /back/** requires ADMIN. /a/** and /p/** allow anonymous access.
Only one active session is allowed per account.
CSRF protection applies to all currently exposed state-changing endpoints, including anonymous POST endpoints under /a/** and /p/**. Server-rendered pages expose the token through:
<meta name="csrf-token" content="...">
<meta name="csrf-header" content="X-CSRF-TOKEN">JSON and multipart requests should send the token using the supplied header name. HTML forms should submit the _csrf hidden field.
X-CSRF-TOKEN: <token>CSRF failures are handled directly by Spring Security and do not pass through the application exception handler.
Business endpoints return 200 OK on success. JSON endpoints use a { "data": ... } envelope; sensitive profile changes return 302 Location: /login.
Errors also use the JSON envelope. Validation and account workflow failures return 400 Bad Request; content authorization failures return 403 Forbidden; missing posts, comments, or public users return 404 Not Found; duplicate post titles return 409 Conflict; unexpected failures return 500 Internal Server Error. Authentication and CSRF failures may be handled directly by Spring Security.
Detailed business messages are returned for profile changes, content validation, moderation constraints, and content 403/404/409 responses. Other account workflows deliberately return the generic {"data":"Request error"} message to avoid exposing sensitive account state. Oversized uploads return {"data":"Max upload size exceeded"}.
Spring Security handles form-login failures directly rather than passing them through the global exception handler: the server returns 302 Location: /login?error, and the login page displays a generic failure message based on the error query parameter. Unauthenticated access to other protected endpoints may also be redirected to /login instead of receiving a JSON 401 response.
| Method | Path | Authentication | CSRF | Description |
|---|---|---|---|---|
POST |
/a/register/verify |
Public | Required | Send registration verification code |
POST |
/a/register |
Public | Required | Register an account |
POST |
/a/reset-password/verify |
Public | Required | Send password-reset verification code |
POST |
/a/reset-password |
Public | Required | Reset password |
POST |
/a/login |
Public | Required | Log in |
POST |
/logout |
Authenticated | Required | Log out |
GET |
/u/me |
USER or ADMIN |
No | Get current user profile |
POST |
/u/me/modify/name |
USER or ADMIN |
Required | Update username |
POST |
/u/me/modify/description |
USER or ADMIN |
Required | Update description |
POST |
/u/me/modify/password |
USER or ADMIN |
Required | Update password |
POST |
/u/me/modify/email/verify |
USER or ADMIN |
Required | Send new-email verification code |
POST |
/u/me/modify/email |
USER or ADMIN |
Required | Update email |
POST |
/u/me/modify/avatar |
USER or ADMIN |
Required | Upload current user avatar |
GET |
/back/users |
ADMIN |
No | List users with pagination |
POST |
/back/modify/user |
ADMIN |
Required | Update all managed fields for a user |
POST |
/back/create/user |
ADMIN |
Required | Create a user |
POST |
/back/delete/user |
ADMIN |
Required | Delete a user |
DELETE |
/back/posts/{postId} |
ADMIN |
Required | Delete any post and its discussion data |
DELETE |
/back/comments/{commentId} |
ADMIN |
Required | Moderate any comment |
GET |
/p/users/{id} |
Public | No | Get a public user profile |
GET |
/p/users/{id}/avatar |
Public | No | Resolve a public user's avatar |
GET |
/avatars/{filename} |
Public | No | Read an avatar file |
GET |
/p/posts |
Public | No | List posts by hot score |
GET |
/p/posts/hot |
Public | No | Get the hottest posts |
GET |
/p/posts/search/author |
Public | No | Search posts by author name |
GET |
/p/posts/author/{authorId} |
Public | No | List posts by author ID |
GET |
/p/posts/search/title |
Public | No | Search posts by title |
GET |
/p/posts/search/tag |
Public | No | Search posts by tag |
GET |
/p/posts/{postId} |
Public | No | Read a post and record an effective view |
POST |
/u/posts |
USER or ADMIN |
Required | Create a post |
PUT |
/u/posts/{postId} |
USER or ADMIN |
Required | Replace the current user's post content |
DELETE |
/u/posts/{postId} |
USER or ADMIN |
Required | Delete the current user's post |
GET |
/p/posts/{postId}/comments |
Public | No | List root comments |
GET |
/p/comments/{commentId}/replies |
Public | No | List direct replies |
POST |
/u/posts/{postId}/comments |
USER or ADMIN |
Required | Publish a root comment |
POST |
/u/comments/{commentId}/replies |
USER or ADMIN |
Required | Reply to a comment |
DELETE |
/u/comments/{commentId} |
USER or ADMIN |
Required | Delete the current user's comment |
PUT |
/u/posts/{postId}/like |
USER or ADMIN |
Required | Like a post |
DELETE |
/u/posts/{postId}/like |
USER or ADMIN |
Required | Remove a post like |
PUT |
/u/comments/{commentId}/like |
USER or ADMIN |
Required | Like a comment |
DELETE |
/u/comments/{commentId}/like |
USER or ADMIN |
Required | Remove a comment like |
POST /a/register/verify
Sends an email verification code for registration. The email address must not already be registered.
Content-Type: application/json
X-CSRF-TOKEN: <token>{
"email": "user@example.com",
"scene": "REGISTER"
}Field requirements:
email: Required and must match a valid email format.scene: Must beREGISTER.
Success: 200 OK, application/json
{
"data": null
}POST /a/register
Creates an account using a registration verification code. Registration does not automatically create a login session.
Content-Type: application/json
X-CSRF-TOKEN: <token>{
"username": "alice",
"password": "secret123",
"email": "user@example.com",
"verificationCode": "A1B2C3"
}Field requirements:
username: Required, non-blank, no regular space characters, at most 32 characters, and unique.password: Required, no regular space characters, and 6 to 32 characters long.email: Required, validly formatted, and unique.verificationCode: Required; its length must match the verification configuration, which defaults to 6.
Success: 200 OK, application/json
{
"data": {
"message": "Register successfully",
"name": "alice"
}
}POST /a/reset-password/verify
Sends a high-risk operation verification code to an existing account email.
Content-Type: application/json
X-CSRF-TOKEN: <token>{
"email": "user@example.com",
"scene": "HIGH_RISK"
}Field requirements:
email: Required, validly formatted, and must belong to an existing account.scene: Must beHIGH_RISK.
Success: 200 OK, application/json
{
"data": null
}POST /a/reset-password
The verification code and email must belong to the same account. On success, the code is invalidated and matching existing sessions are expired through the session registry.
Content-Type: application/json
X-CSRF-TOKEN: <token>{
"email": "user@example.com",
"newPassword": "newSecret123",
"verificationCode": "D4E5F6"
}Field requirements:
email: Required, validly formatted, and must belong to an existing account.newPassword: Required, no regular space characters, and 6 to 32 characters long.verificationCode: Required; its length must match the verification configuration.
Success: 200 OK, application/json
{
"data": "Password reset successfully"
}POST /a/login
This endpoint is provided by Spring Security form login and does not accept JSON.
Content-Type: application/x-www-form-urlencodedusername=alice&password=secret123&_csrf=<token>
- Success: sets the session cookie and redirects with
302to/dashboard. - Failure: redirects back to the login page with
302 Location: /login?error.GET /login?errorrenders the login page with the generic "Incorrect username or password" message; the failure response is not JSON.
POST /logout
Requires an active session and a CSRF form field.
_csrf=<token>
On success, the current session is invalidated and the client is redirected with 302 to /login?logout by default.
GET /u/me
Authentication: required (USER or ADMIN)
Success: 200 OK, application/json
{
"data": {
"userId": 1,
"userName": "alice",
"userEmail": "user@example.com",
"description": "Hello",
"createdAt": "2026-07-29T12:00:00",
"enabled": true
}
}createdAt uses Jackson's default ISO-8601 JSON representation for LocalDateTime.
POST /u/me/modify/name with {"newName":"alice2"}. The target user is always taken from the authenticated session. Success returns 302 Location: /login.
POST /u/me/modify/description with {"newDescription":"About me"}.
POST /u/me/modify/password with {"oldPassword":"old","newPassword":"newSecret"}. Success returns 302 Location: /login.
Call /u/me/modify/email/verify with {"email":"new@example.com","scene":"RESET_EMAIL"}, then submit {"email":"new@example.com","password":"currentPassword","verificationCode":"ABC123"} to /u/me/modify/email. Success returns 302 Location: /login.
POST /u/me/modify/avatar
Authentication: required (USER or ADMIN)
Content-Type: multipart/form-data; boundary=...
X-CSRF-TOKEN: <token>| Name | Type | Required | Description |
|---|---|---|---|
file |
File | Yes | New avatar image |
Requirements:
- The file must be non-empty and its MIME type must start with
image/. - The backend decodes the actual image content instead of trusting the filename extension.
- PNG, JPEG, and GIF are supported.
- Both the default per-file and total request limits are 10 MB.
- The account must be enabled.
Success: 200 OK with {"data":null}.
GET /back/users
Authentication: ADMIN only. Supports Spring Data Pageable query parameters:
| Parameter | Example | Description |
|---|---|---|
page |
0 |
Zero-based page index |
size |
20 |
Number of records per page, clamped to 1..100 |
sort |
id,asc |
Sort property and direction; may be repeated |
Example: GET /back/users?page=0&size=20&sort=id,asc
The successful response stores a stable pagination object in data:
{
"data": {
"content": [
{
"id": 2,
"username": "alice",
"email": "alice@example.com",
"avatarId": 2,
"role": "USER",
"enabled": true,
"createdAt": "2026-07-29T12:00:00",
"updatedAt": "2026-07-30T12:00:00"
}
],
"page": 0,
"size": 20,
"totalPages": 1,
"totalElements": 1,
"first": true,
"last": true
}
}POST /back/modify/user
Authentication: ADMIN only. Requires JSON and a CSRF header. Every save must include all five fields, including fields whose values did not change:
{
"id": 2,
"username": "alice",
"email": "alice@example.com",
"role": "USER",
"enabled": true
}Field requirements:
id: Required and must identify an existing user.username: Required, non-empty, must not contain@or a regular space, and at most 32 characters.email: Required, non-empty, and must match the configured email regular expression.role: Required; valid values areUSERandADMIN.enabled: Required boolean. The DTO uses a primitive value, so omission binds asfalse; clients must not omit it.
The system always requires at least one enabled ADMIN; a change violating this rule is rolled back. After a successful update, all sessions for the target user that match in the session registry are marked expired. If an administrator updates their own account, the admin page redirects to login.
Success: 200 OK; data contains the updated user view with the same fields as a list item.
POST /back/create/user
Authentication: ADMIN only. Requires JSON and a CSRF header. The request must include all five fields:
{
"username": "alice",
"email": "alice@example.com",
"password": "secret1",
"role": "USER",
"enabled": true
}Field requirements:
username: Required, non-empty, must not contain@or a regular space, and at most 32 characters.email: Required, non-empty, and must match the configured email regular expression.password: Required, non-empty, must not contain regular spaces, and 6 to 64 characters long.role: Required; valid values areUSERandADMIN.enabled: Must explicitly betrue; the current backend does not allow creating a disabled user directly.
The password is stored using BCrypt. Success returns 200 OK; data contains the new user view and never includes the password or password hash.
POST /back/delete/user
Authentication: ADMIN only. Requires JSON and a CSRF header. The request uses the complete last-known snapshot returned by the server:
{
"id": 2,
"username": "alice",
"email": "alice@example.com",
"role": "USER",
"enabled": true
}id, username, email, and role must match the stored user or the request fails. The admin page also sends enabled. On success, matching target sessions are marked expired and the response is {"data":true}. The service checks that an enabled administrator remains in the system.
DELETE /back/posts/{postId}
Authentication: ADMIN only. A CSRF token is required and the request has no body. The administrator may delete a post regardless of its author. The operation runs in one transaction and removes the post's comments, comment likes, post likes, tag rows, and view-deduplication cache entries before the post becomes inaccessible.
Success: 200 OK. An unknown post returns 404 Not Found.
DELETE /back/comments/{commentId}
Authentication: ADMIN only. A CSRF token is required and the request has no body. The administrator may moderate a comment regardless of its publisher. A leaf comment is physically removed; a comment with replies is converted into the same deleted placeholder used by author deletion so that replies remain reachable. Likes and active comment counters are updated transactionally. Repeating the request for an existing soft-deleted placeholder is idempotent.
Success: 200 OK. An unknown comment returns 404 Not Found.
GET /p/users/{id}
Anonymous access is allowed and no CSRF token is needed. Disabled or unknown users return 404 Not Found. The response uses the standard data envelope:
{
"data": {
"userId": 2,
"userName": "alice",
"description": "Backend developer",
"avatarUrl": "/p/users/2/avatar",
"postCount": 3,
"createdAt": "2026-07-29T12:00:00"
}
}GET /p/users/{id}/avatar
Returns 302 Found with a Location header pointing to the user's current image or the default avatar. Disabled or unknown users return 404 Not Found.
GET /avatars/{filename}
Public. Reads a file from the configured kistack.data.path/avatars directory. The content type is determined by the static resource handler and file type.
GET /avatars/default.pngThese routes return Thymeleaf HTML pages rather than JSON APIs.
| Method | Path | Access | Page |
|---|---|---|---|
GET |
/login |
Public | Login page |
GET |
/register |
Public | Registration page |
GET |
/reset-password |
Public | Password-reset page |
GET |
/ or /index |
Public | Post index and search page |
GET |
/posts/{postId} |
Public | Post detail and comments page |
GET |
/users/{userId} |
Public | Public user profile page |
GET |
/dashboard |
USER or ADMIN |
User profile dashboard |
GET |
/editor |
USER or ADMIN |
Create-post editor |
GET |
/editor/{postId} |
USER or ADMIN |
Edit-post editor |
GET |
/back/dashboard |
ADMIN |
Administrator user-management page |
GET /favicon.svg is a public static resource.
All list endpoints return a stable page object inside the normal data envelope:
{
"data": {
"content": [],
"page": 0,
"size": 20,
"totalElements": 0,
"totalPages": 0,
"first": true,
"last": true
}
}Post, search, comment, and reply lists are ordered by hotScore DESC, then createdAt DESC and id DESC. page defaults to 0, size defaults to 20, and size is capped at 100. The hot-post endpoint accepts limit, defaults to 20, and caps it at 50.
| Method and path | Query parameters |
|---|---|
GET /p/posts |
page, size |
GET /p/posts/hot |
limit |
GET /p/posts/search/author |
keyword, page, size |
GET /p/posts/author/{authorId} |
page, size |
GET /p/posts/search/title |
keyword, page, size |
GET /p/posts/search/tag |
keyword, page, size |
GET /p/posts/{postId} |
None |
Search is case-insensitive for ASCII text and treats %, _, and ! as ordinary characters. List responses omit full article content. Reading one post returns its content and records at most one view per visitor in the configured deduplication window. Lists and searches never increase view counts.
Old paths such as /p/post/pid={id} and /p/post/title={title} have been removed.
POST /u/posts and PUT /u/posts/{postId} use this body:
{
"title": "Spring Data JPA notes",
"content": "Article content...",
"tags": ["spring", "jpa", "sqlite"]
}The author always comes from the authenticated session. title is limited to 200 characters, content to 100000 characters, and each post must have 1 to 10 tags of at most 64 characters. Tags are trimmed, lowercased, and deduplicated. Only the author can modify or delete a post. Deleting a post also removes its comments and all related likes and tags.
Root comments are read from GET /p/posts/{postId}/comments; direct replies are read from GET /p/comments/{commentId}/replies. Publishing and replying use:
{
"content": "Comment text"
}Comment content is limited to 500 characters. Reply postId and respondentId are derived from the parent comment; clients cannot supply them. A leaf comment is removed physically. A comment with replies is retained as a deleted placeholder so its reply chain remains readable. Users can only delete their own comments.
Post and comment like endpoints are idempotent. A unique database record prevents the same user from increasing a counter twice, and repeated unlike requests cannot make counters negative. Successful responses are shaped as follows:
{
"data": {
"liked": true,
"likeCount": 12
}
}Post hot scores combine logarithmic view, like, and comment signals with time decay. Comment hot scores use likes, direct replies, and time decay. Scores are recalculated after interactions and periodically in the background.
The following behavior can be changed through configuration or environment variables, so deployment values may differ from the defaults in this document:
- Initial administrator password:
KISTACK_ADMIN_PASSWORD(required; no default) - Administrator reset switch:
KISTACK_ADMIN_RESET_ENABLED(defaults tofalse) - Verification code length:
kistack.email.verification.verification-code-length - Email format regular expression:
kistack.email.from.email-format-regex - Per-file size:
spring.servlet.multipart.max-file-size - Total request size:
spring.servlet.multipart.max-request-size - Data directory:
kistack.data.path - Default avatar:
kistack.data.default-avatar - Post-view deduplication window:
kistack.caffeine.post-view-deduplicate-minutes - Post-view cache capacity:
kistack.caffeine.post-view-maximum-size - Hot-score refresh interval:
kistack.hot-score.refresh-interval-ms - Hot-score refresh batch size:
kistack.hot-score.refresh-batch-size