platforms/devplace/rest_posts_and_comments.md

DevPlace REST: Posts and Comments

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.

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 attachmentuids are repeated form keys - upload files first via Uploads and pass the returned uids here. Engage with this content through Votes, Reactions, Bookmarks & Polls.

Every endpoint follows the shared Conventions & Errors (auth, content negotiation, pagination, status codes); see Authentication 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

{
  "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

NameInTypeRequiredDescription
tabqueryenumnoFeed selector. Allowed: all, trending, following.
topicqueryenumnoFilter by topic. Allowed: devlog, showcase, question, rant, fun, random, politics.
searchquerystringnoSearch post title, content, and author username.
beforequerystringnoPagination cursor.

Sample response

{
  "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

NameInTypeRequiredDescription
contentformtextareayesBody, 10-125000 characters.
titleformstringnoOptional title, up to 500 characters.
topicformenumnoPost topic. Allowed: devlog, showcase, question, rant, fun, random, politics.
project_uidformstringnoAttach to a project.
poll_questionformstringnoOptional poll question.
poll_optionsformstringnoRepeat 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

{
  "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

NameInTypeRequiredDescription
post_slugpathstringyesSlug or UID of the post.

Sample response

{
  "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

NameInTypeRequiredDescription
post_slugpathstringyesSlug or UID of the post.
contentformtextareayesBody, 10-125000 characters.
titleformstringnoOptional title.
topicformenumnoPost topic. Allowed: devlog, showcase, question, rant, fun, random, politics.
poll_questionformstringnoOptional poll question. Adds a poll only when the post has none.
poll_optionsformstringnoRepeat the field for each poll option, or send a single newline- or comma-separated string (2-6 options).

Sample response

{
  "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

NameInTypeRequiredDescription
post_slugpathstringyesSlug or UID of the post.

Sample response

{
  "ok": true,
  "redirect": "/feed",
  "data": null
}

POST /comments/create - Create a comment

Comment on any commentable target. Supports nested replies.

Minimal role: Member

Parameters

NameInTypeRequiredDescription
contentformtextareayesBody, 3-1000 characters.
target_uidformstringnoUID of the target (or use post_uid).
post_uidformstringnoConvenience alias for a post target.
target_typeformenumnoType of the target. Allowed: post, project, news, issue, gist, quiz.
parent_uidformstringnoParent comment UID for a reply.

Either targetuid or postuid is required.

Sample response

{
  "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

NameInTypeRequiredDescription
comment_uidpathstringyesUID of the comment.
contentformtextareayesNew body, 3-1000 characters.

Sample response

{
  "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

NameInTypeRequiredDescription
comment_uidpathstringyesUID of the comment.

Sample response

{
  "ok": true,
  "redirect": "/posts/POST_SLUG",
  "data": {
    "deleted_uid": "COMMENT_UID",
    "target_type": "post",
    "target_uid": "TARGET_UID"
  }
}