--- title: "DevPlace REST: Posts and Comments" description: "Endpoints for the home page and feed plus the full create, read, edit and delete cycle for posts and comments, with their form fields, topic enum, attachment_uids handling, pagination cursors and JSON payload shapes." language: null framework: null category: api_design tags: - devplace - api - http-api - posts - comments - feed - crud - rest - pagination - attachments keywords: - devplace POST /posts/create form fields - devplace GET /feed tab topic search before cursor - devplace comment create edit delete endpoint - devplace post json payload author attachments - devplace attachment_uids repeated form key last_updated: 2026-08-12 difficulty: intermediate version: "DevPlace (devplace.net), documented 2026-08" related: - rest_uploads.md - rest_votes_reactions_bookmarks_polls.md - conventions_and_errors.md - authentication.md - README.md search_priority: high status: published --- # DevPlace REST: Posts and Comments The core content types. Read endpoints render HTML pages; write endpoints accept form fields and redirect to the new or updated resource. List fields such as `attachment_uids` are repeated form keys - upload files first via [Uploads](rest_uploads.md) and pass the returned uids here. Engage with this content through [Votes, Reactions, Bookmarks & Polls](rest_votes_reactions_bookmarks_polls.md). Every endpoint follows the shared [Conventions & Errors](conventions_and_errors.md) (auth, content negotiation, pagination, status codes); see [Authentication](authentication.md) for the four ways to sign requests. ## `GET /` - Home The home page. Guests see the marketing splash; authenticated users see a personalized home (welcome, feed shortcut, latest posts, news). It no longer redirects to /feed. The latest-posts section interleaves authors so no two consecutive posts share an author. *Minimal role:* Public **Sample response** ```json { "is_authenticated": false, "user_post_count": 0, "user_stars": 0, "user_xp": 0, "user_level": 0, "xp_progress_pct": 0, "unread_count": 0, "landing_articles": [ { "uid": "UID", "slug": "slug", "title": "Title", "description": "text", "url": "/path", "source_name": "string", "grade": 0, "featured": 0, "synced_at": "2026-01-01T00:00:00+00:00", "time_ago": "2 hours ago", "image_url": "/path" } ], "landing_posts": [ { "post": null, "author": { "uid": "UID", "username": "username", "avatar_seed": "string", "bio": "text", "location": "string", "git_link": "string", "website": "string", "level": 0, "xp": 0, "xp_progress_pct": 0, "xp_next_level": 0, "stars": 0, "created_at": "2026-01-01T00:00:00+00:00", "last_seen": "string" }, "time_ago": "2 hours ago", "comment_count": 0, "stars": 0, "slug": "slug" } ], "top_contributors": [], "trending_topics": [ { "topic": "random", "count": 0 } ] } ``` ## `GET /feed` - Browse the feed The main post feed. Returns an HTML page. Each page interleaves authors so no two consecutive posts share an author. *Minimal role:* Public **Parameters** | Name | In | Type | Required | Description | |------|----|------|----------|-------------| | `tab` | query | enum | no | Feed selector. Allowed: all, trending, following. | | `topic` | query | enum | no | Filter by topic. Allowed: devlog, showcase, question, rant, fun, random, politics. | | `search` | query | string | no | Search post title, content, and author username. | | `before` | query | string | no | Pagination cursor. | **Sample response** ```json { "posts": [ { "post": { "uid": "UID", "slug": "slug", "user_uid": "UID", "title": "Title", "content": "text", "topic": "random", "stars": 0, "image": "string", "project_uid": "UID", "created_at": "2026-01-01T00:00:00+00:00", "updated_at": "2026-01-01T00:00:00+00:00" }, "maturity": "string", "author": { "uid": "UID", "username": "username", "avatar_seed": "string", "bio": "text", "location": "string", "git_link": "string", "website": "string", "level": 0, "xp": 0, "xp_progress_pct": 0, "xp_next_level": 0, "stars": 0, "created_at": "2026-01-01T00:00:00+00:00", "last_seen": "string" }, "time_ago": "2 hours ago", "my_vote": 0, "comment_count": 0, "attachments": [ { "uid": "UID", "filename": "string", "url": "/path", "size": 0, "is_image": false, "is_video": false, "mime_type": "string", "created_at": "2026-01-01T00:00:00+00:00", "can_modify": false } ], "recent_comments": [ { "comment": { "uid": "UID", "user_uid": "UID", "content": "text", "parent_uid": "UID", "target_type": "string", "target_uid": "UID", "created_at": "2026-01-01T00:00:00+00:00" }, "author": { "uid": "UID", "username": "username", "avatar_seed": "string", "bio": "text", "location": "string", "git_link": "string", "website": "string", "level": 0, "xp": 0, "xp_progress_pct": 0, "xp_next_level": 0, "stars": 0, "created_at": "2026-01-01T00:00:00+00:00", "last_seen": "string" }, "time_ago": "2 hours ago", "votes": { "up": 0, "down": 0 }, "my_vote": 0, "children": [], "attachments": [ { "uid": "UID", "filename": "string", "url": "/path", "size": 0, "is_image": false, "is_video": false, "mime_type": "string", "created_at": "2026-01-01T00:00:00+00:00", "can_modify": false } ], "reactions": { "counts": {}, "mine": [] } } ], "reactions": { "counts": {}, "mine": [] }, "bookmarked": false, "poll": { "uid": "UID", "question": "string", "options": [ { "uid": "UID", "label": "string", "count": 0, "votes": 0, "pct": 0 } ], "total": 0, "my_choice": "string", "voted": "string" }, "project_link": { "uid": "UID", "name": "string", "slug": "slug", "url": "/path" } } ], "current_tab": "string", "current_topic": "random", "search": "string", "next_cursor": "2026-01-01T00:00:00+00:00", "total_members": 0, "posts_today": 0, "total_projects": 0, "total_gists": 0, "top_authors": [ { "uid": "UID", "username": "username", "avatar_seed": "string", "bio": "text", "location": "string", "git_link": "string", "website": "string", "level": 0, "xp": 0, "xp_progress_pct": 0, "xp_next_level": 0, "stars": 0, "created_at": "2026-01-01T00:00:00+00:00", "last_seen": "string" } ], "daily_topic": null, "online_users": [ { "uid": "UID", "username": "username", "avatar_seed": "string", "bio": "text", "location": "string", "git_link": "string", "website": "string", "level": 0, "xp": 0, "xp_progress_pct": 0, "xp_next_level": 0, "stars": 0, "created_at": "2026-01-01T00:00:00+00:00", "last_seen": "string" } ] } ``` ## `POST /posts/create` - Create a post Publish a post, optionally with a poll. Redirects to the new post. *Minimal role:* Member **Parameters** | Name | In | Type | Required | Description | |------|----|------|----------|-------------| | `content` | form | textarea | yes | Body, 10-125000 characters. | | `title` | form | string | no | Optional title, up to 500 characters. | | `topic` | form | enum | no | Post topic. Allowed: devlog, showcase, question, rant, fun, random, politics. | | `project_uid` | form | string | no | Attach to a project. | | `poll_question` | form | string | no | Optional poll question. | | `poll_options` | form | string | no | Repeat the field for each poll option, or send a single newline- or comma-separated string (2-6 options). | > Returns a `302` redirect to `/posts/{slug}` on success. **Sample response** ```json { "ok": true, "redirect": "/posts/POST_SLUG", "data": { "uid": "POST_UID", "slug": "POST_SLUG", "url": "/posts/POST_SLUG" } } ``` ## `GET /posts/{post_slug}` - View a post Render a post with comments. Returns an HTML page. *Minimal role:* Public **Parameters** | Name | In | Type | Required | Description | |------|----|------|----------|-------------| | `post_slug` | path | string | yes | Slug or UID of the post. | **Sample response** ```json { "maturity": "string", "post": { "uid": "UID", "slug": "slug", "user_uid": "UID", "title": "Title", "content": "text", "topic": "random", "stars": 0, "image": "string", "project_uid": "UID", "created_at": "2026-01-01T00:00:00+00:00", "updated_at": "2026-01-01T00:00:00+00:00" }, "author": { "uid": "UID", "username": "username", "avatar_seed": "string", "bio": "text", "location": "string", "git_link": "string", "website": "string", "level": 0, "xp": 0, "xp_progress_pct": 0, "xp_next_level": 0, "stars": 0, "created_at": "2026-01-01T00:00:00+00:00", "last_seen": "string" }, "is_owner": false, "star_count": 0, "my_vote": 0, "time_ago": "2 hours ago", "comments": [ { "comment": { "uid": "UID", "user_uid": "UID", "content": "text", "parent_uid": "UID", "target_type": "string", "target_uid": "UID", "created_at": "2026-01-01T00:00:00+00:00" }, "author": { "uid": "UID", "username": "username", "avatar_seed": "string", "bio": "text", "location": "string", "git_link": "string", "website": "string", "level": 0, "xp": 0, "xp_progress_pct": 0, "xp_next_level": 0, "stars": 0, "created_at": "2026-01-01T00:00:00+00:00", "last_seen": "string" }, "time_ago": "2 hours ago", "votes": { "up": 0, "down": 0 }, "my_vote": 0, "children": [], "attachments": [ { "uid": "UID", "filename": "string", "url": "/path", "size": 0, "is_image": false, "is_video": false, "mime_type": "string", "created_at": "2026-01-01T00:00:00+00:00", "can_modify": false } ], "reactions": { "counts": {}, "mine": [] } } ], "attachments": [ { "uid": "UID", "filename": "string", "url": "/path", "size": 0, "is_image": false, "is_video": false, "mime_type": "string", "created_at": "2026-01-01T00:00:00+00:00", "can_modify": false } ], "reactions": { "counts": {}, "mine": [] }, "bookmarked": false, "poll": { "uid": "UID", "question": "string", "options": [ { "uid": "UID", "label": "string", "count": 0, "votes": 0, "pct": 0 } ], "total": 0, "my_choice": "string", "voted": "string" }, "comment_count": 0, "related_posts": [ { "post": { "uid": "UID", "slug": "slug", "user_uid": "UID", "title": "Title", "content": "text", "topic": "random", "stars": 0, "image": "string", "project_uid": "UID", "created_at": "2026-01-01T00:00:00+00:00", "updated_at": "2026-01-01T00:00:00+00:00" }, "maturity": "string", "author": { "uid": "UID", "username": "username", "avatar_seed": "string", "bio": "text", "location": "string", "git_link": "string", "website": "string", "level": 0, "xp": 0, "xp_progress_pct": 0, "xp_next_level": 0, "stars": 0, "created_at": "2026-01-01T00:00:00+00:00", "last_seen": "string" }, "time_ago": "2 hours ago", "my_vote": 0, "comment_count": 0, "attachments": [ { "uid": "UID", "filename": "string", "url": "/path", "size": 0, "is_image": false, "is_video": false, "mime_type": "string", "created_at": "2026-01-01T00:00:00+00:00", "can_modify": false } ], "recent_comments": [ { "comment": { "uid": "UID", "user_uid": "UID", "content": "text", "parent_uid": "UID", "target_type": "string", "target_uid": "UID", "created_at": "2026-01-01T00:00:00+00:00" }, "author": { "uid": "UID", "username": "username", "avatar_seed": "string", "bio": "text", "location": "string", "git_link": "string", "website": "string", "level": 0, "xp": 0, "xp_progress_pct": 0, "xp_next_level": 0, "stars": 0, "created_at": "2026-01-01T00:00:00+00:00", "last_seen": "string" }, "time_ago": "2 hours ago", "votes": { "up": 0, "down": 0 }, "my_vote": 0, "children": [], "attachments": [ { "uid": "UID", "filename": "string", "url": "/path", "size": 0, "is_image": false, "is_video": false, "mime_type": "string", "created_at": "2026-01-01T00:00:00+00:00", "can_modify": false } ], "reactions": { "counts": {}, "mine": [] } } ], "reactions": { "counts": {}, "mine": [] }, "bookmarked": false, "poll": { "uid": "UID", "question": "string", "options": [ { "uid": "UID", "label": "string", "count": 0, "votes": 0, "pct": 0 } ], "total": 0, "my_choice": "string", "voted": "string" }, "project_link": { "uid": "UID", "name": "string", "slug": "slug", "url": "/path" } } ], "topics": [ "random" ], "project_link": { "uid": "UID", "name": "string", "slug": "slug", "url": "/path" } } ``` ## `POST /posts/edit/{post_slug}` - Edit a post Update a post you own, optionally adding a poll if it has none. *Minimal role:* Member **Parameters** | Name | In | Type | Required | Description | |------|----|------|----------|-------------| | `post_slug` | path | string | yes | Slug or UID of the post. | | `content` | form | textarea | yes | Body, 10-125000 characters. | | `title` | form | string | no | Optional title. | | `topic` | form | enum | no | Post topic. Allowed: devlog, showcase, question, rant, fun, random, politics. | | `poll_question` | form | string | no | Optional poll question. Adds a poll only when the post has none. | | `poll_options` | form | string | no | Repeat the field for each poll option, or send a single newline- or comma-separated string (2-6 options). | **Sample response** ```json { "ok": true, "redirect": "/posts/POST_SLUG", "data": { "uid": "POST_UID", "slug": "POST_SLUG", "url": "/posts/POST_SLUG" } } ``` ## `POST /posts/delete/{post_slug}` - Delete a post Delete a post you own; administrators may delete any user's post. Soft-deleted (hidden everywhere but restorable from admin trash) and cascades its comments and votes. *Minimal role:* Member **Parameters** | Name | In | Type | Required | Description | |------|----|------|----------|-------------| | `post_slug` | path | string | yes | Slug or UID of the post. | **Sample response** ```json { "ok": true, "redirect": "/feed", "data": null } ``` ## `POST /comments/create` - Create a comment Comment on any commentable target. Supports nested replies. *Minimal role:* Member **Parameters** | Name | In | Type | Required | Description | |------|----|------|----------|-------------| | `content` | form | textarea | yes | Body, 3-1000 characters. | | `target_uid` | form | string | no | UID of the target (or use post_uid). | | `post_uid` | form | string | no | Convenience alias for a post target. | | `target_type` | form | enum | no | Type of the target. Allowed: post, project, news, issue, gist, quiz. | | `parent_uid` | form | string | no | Parent comment UID for a reply. | > Either `target_uid` or `post_uid` is required. **Sample response** ```json { "ok": true, "redirect": "/posts/POST_SLUG#comment-COMMENT_UID", "data": { "uid": "COMMENT_UID", "url": "/posts/POST_SLUG#comment-COMMENT_UID" } } ``` ## `POST /comments/edit/{comment_uid}` - Edit a comment Edit the body of a comment you own. Returns the updated comment. *Minimal role:* Member **Parameters** | Name | In | Type | Required | Description | |------|----|------|----------|-------------| | `comment_uid` | path | string | yes | UID of the comment. | | `content` | form | textarea | yes | New body, 3-1000 characters. | **Sample response** ```json { "uid": "COMMENT_UID", "content": "Edited body.", "url": "/posts/POST_SLUG#comment-COMMENT_UID", "updated_at": "2026-06-15T12:00:00+00:00" } ``` ## `POST /comments/delete/{comment_uid}` - Delete a comment Delete a comment you own; administrators may delete any user's comment. Soft-deleted (hidden everywhere but restorable from admin trash). *Minimal role:* Member **Parameters** | Name | In | Type | Required | Description | |------|----|------|----------|-------------| | `comment_uid` | path | string | yes | UID of the comment. | **Sample response** ```json { "ok": true, "redirect": "/posts/POST_SLUG", "data": { "deleted_uid": "COMMENT_UID", "target_type": "post", "target_uid": "TARGET_UID" } } ```