--- title: "DevPlace devRant-Compatible REST API" description: "The devRant compatibility layer: base URL and response shape, integer id mapping, the token-triple authentication model, and the rants, comments, users and notifications endpoints, with ready-made client scripts." language: null framework: null category: api_design tags: - devplace - api - http-api - devrant - compatibility - rest - api-design - integer-ids - client-library - migration keywords: - devrant api compatible endpoints devplace - devrant auth token token_id token_key user_id - devrant /api/devrant/rants list - devrant field mapping uid integer id - devrant client script python javascript last_updated: 2026-08-12 difficulty: intermediate version: "DevPlace (devplace.net), documented 2026-08" related: - authentication.md - README.md - conventions_and_errors.md search_priority: normal status: published --- # DevPlace devRant-Compatible REST API ## Overview DevPlace exposes a second REST protocol under `https://devplace.net/api` that reproduces the public [devRant](https://devrant.com) API shape on DevPlace data, so legacy devRant clients can run against this server unchanged. Rants are DevPlace posts, comments and votes are the native engagement layer, and every devRant action is funnelled through the same audited helpers as the website (so XP, notifications, and soft-delete all apply). This reference is split across focused pages: - [Authentication & accounts](#authentication-and-accounts) - login, registration, the token triple. - [Rants](#rants) - feed, single rant, create, edit, delete, vote, favorite, search. - [Comments](#comments) - read, post, edit, delete, vote. - [Users & avatars](#users-and-avatars) - profiles, username lookup, profile edit, avatars. - [Notifications](#notifications) - the notification feed. - [Client scripts](#client-scripts) - ready-to-run Python and JavaScript clients. ### Base URL and shape Every endpoint lives under `https://devplace.net/api` and returns JSON with a `success` boolean. On success the payload sits beside it; on a logical failure the response is `{ "success": false, "error": "..." }`. HTTP status is `200` for logical failures, except a bad login which returns `400` (matching devRant). Requests accept parameters as query string (`GET`/`DELETE`) or as a form body or JSON body (`POST`). ### Integer IDs devRant identifies everything by integer. DevPlace maps those directly onto the auto-increment `id` that every table already carries, so `rant_id` is a post's `id`, `comment_id` is a comment's `id`, and `user_id` is a user's `id`. There is no separate id space to track. ### Authentication model Write operations need the devRant token triple. `POST /api/users/auth-token` with a username (or email) and password returns an `auth_token` object; you then send `user_id`, `token_id`, and `token_key` with every request. Read endpoints (feed, single rant, search, profiles) work without authentication. See [Authentication & accounts](#authentication-and-accounts). ### Field mapping at a glance | devRant concept | DevPlace mapping | |-----------------|------------------| | `rant` | post (topic forced to `rant`; `text` is `title` + body when a title exists) | | `tags` | stored verbatim on the post and returned as-is (falls back to `[topic]`) | | `comment` | comment with `target_type = post` | | `vote` (`1`/`-1`/`0`) | upvote / downvote / clear on the native vote layer | | `favorite` / `unfavorite` | bookmark add / remove | | `user_avatar` | a real PNG rendered from the username (see [Users & avatars](#users-and-avatars)) | | `profile.skills` | derived from the user bio (DevPlace has no separate skills field) | ### Availability The protocol is toggled by the `devrant_api_enabled` site setting (default on); when off, every `/api` path returns `404`. Legacy clients hard-coded to `devrant.com` reach this server only through host routing (DNS / reverse-proxy), which is an infrastructure concern. ## Authentication and accounts Write operations authenticate with the devRant token triple. Log in once below and every authenticated widget across these pages becomes runnable (the token is kept in your browser only). Read operations ([rants](#rants), [profiles](#users-and-avatars)) need no authentication. `POST /api/users/auth-token` returns an `auth_token` whose `id` is the `token_id`, `key` is the `token_key`, and `user_id` is the integer user id; those three are sent automatically by these widgets (query params for `GET`/`DELETE`, form body for `POST`). A bad login returns HTTP `400`. See the [overview](#overview) for the response envelope. A DevRant auth token (the `key` field) also works on the **main DevPlace API**: use it as a Bearer token or `X-API-KEY` header on any DevPlace endpoint. See [Authentication](authentication.md) for details. DevPlace also has its own native token endpoint at `POST /auth/token` - see the [Authentication](authentication.md) page.
POST /api/users/auth-token Minimal role: Public

Log in

Authenticate with username (or email) and password. Running this here logs you in for every widget on these pages.

POST /api/users Minimal role: Public

Register

Create a new account. Returns an auth token (you are logged in immediately).

DELETE /api/users/me Minimal role: Member

Deactivate account

Deactivate the logged-in account and revoke its tokens.

## Rants A rant is a DevPlace post. Reads are public; writes need the token triple - log in with the bar below (or on the [Authentication](#authentication-and-accounts) page) and run any widget live. The token triple is injected automatically. `text` is the post's title and body combined; devRant `tags` round-trip verbatim. Posting a comment lives here too; editing and voting on a comment is on the [Comments](#comments) page.
GET /api/devrant/rants Minimal role: Public

Rant feed

List rants. Sort by recent, top, or algo.

GET /api/devrant/rants/{rant_id} Minimal role: Public

Single rant with comments

Fetch one rant and its comments.

POST /api/devrant/rants Minimal role: Member

Post a rant

Create a new rant. Tags are comma-separated and stored verbatim.

POST /api/devrant/rants/{rant_id} Minimal role: Member

Edit a rant

Replace a rant's text and tags (owner only).

DELETE /api/devrant/rants/{rant_id} Minimal role: Member

Delete a rant

Soft-delete a rant (owner or admin).

POST /api/devrant/rants/{rant_id}/vote Minimal role: Member

Vote on a rant

Upvote (1), downvote (-1), or clear (0). Returns the updated rant.

POST /api/devrant/rants/{rant_id}/favorite Minimal role: Member

Favorite a rant

Bookmark a rant.

POST /api/devrant/rants/{rant_id}/unfavorite Minimal role: Member

Unfavorite a rant

Remove a rant bookmark.

POST /api/devrant/rants/{rant_id}/comments Minimal role: Member

Comment on a rant

Post a comment on a rant.

## Comments A comment is a DevPlace comment on a post, identified by its integer `id`. Posting a comment on a rant is on the [Rants](#rants) page; the widgets below read, edit, delete, and vote on an existing comment. Log in below to enable the authenticated widgets.
GET /api/comments/{comment_id} Minimal role: Public

Get a comment

Fetch a single comment by id.

POST /api/comments/{comment_id} Minimal role: Member

Edit a comment

Replace a comment's text (owner only).

DELETE /api/comments/{comment_id} Minimal role: Member

Delete a comment

Soft-delete a comment (owner or admin).

POST /api/comments/{comment_id}/vote Minimal role: Member

Vote on a comment

Upvote (1), downvote (-1), or clear (0).

## Users and avatars Profiles are public; editing your own needs the token triple (log in below). `score` is the user's net stars, `about` is the bio, `github` is the git link, and `skills` is derived from the bio. The nested `content.content` holds the user's serialized rants and comments.
GET /api/get-user-id Minimal role: Public

Resolve username to id

Look up a user's integer id from their username.

GET /api/users/{user_id} Minimal role: Public

Get a profile

Fetch a user's devRant profile with their rants and comments.

POST /api/users/me/edit-profile Minimal role: Member

Edit your profile

Update bio, location, git link, and website. profile_skills is accepted but ignored (skills are derived from the bio).

GET /api/avatars/u/{username}.png Minimal role: Public

Avatar image

Every user_avatar.i points at this path. It renders a real PNG from the username seed; pass ?size= (16-512, default 128).

Example devRant avatar
## Notifications The notification feed maps DevPlace notifications onto the devRant shape. Both endpoints need the token triple - log in below. DevPlace types map onto devRant types: `comment`/`reply` to `comment_discuss`, `mention` to `comment_mention`, `vote` to `rant_vote`, `follow` to `rant_sub`.
GET /api/users/me/notif-feed Minimal role: Member

Notification feed

Fetch the logged-in user's notification feed with unread counts.

DELETE /api/users/me/notif-feed Minimal role: Member

Clear notifications

Mark every notification read.

## Client scripts Ready-to-run clients for the devRant-compatible protocol. The Python client uses only the standard library; the JavaScript client uses Node 18+ (global `fetch`). The full versions, plus example scripts and an end-to-end conformance test, ship in the repository under `examples/devrant/`. ### Python client (drop-in) ```python import json, urllib.parse, urllib.request class DevRant: def __init__(self, base_url, username=None, password=None): self.base_url = base_url.rstrip("/") self.username, self.password = username, password self.auth = {} def _request(self, method, path, params=None, body=None): merged = dict(params or {}); merged.update(self.auth) url = f"{self.base_url}/api/{path.lstrip('/')}" data, headers = None, {"Accept": "application/json"} if method in ("GET", "DELETE"): if merged: url += "?" + urllib.parse.urlencode(merged) else: payload = dict(merged); payload.update(body or {}) data = urllib.parse.urlencode(payload).encode() headers["Content-Type"] = "application/x-www-form-urlencoded" req = urllib.request.Request(url, data=data, headers=headers, method=method) with urllib.request.urlopen(req) as resp: return json.loads(resp.read().decode()) def login(self): out = self._request("POST", "users/auth-token", body={"username": self.username, "password": self.password}) if not out.get("success"): raise RuntimeError(out.get("error", "login failed")) t = out["auth_token"] self.auth = {"user_id": t["user_id"], "token_id": t["id"], "token_key": t["key"]} return self.auth def rants(self, sort="recent", limit=20, skip=0): return self._request("GET", "devrant/rants", {"sort": sort, "limit": limit, "skip": skip}).get("rants", []) def post_rant(self, text, tags=""): return self._request("POST", "devrant/rants", body={"rant": text, "tags": tags}) def vote_rant(self, rant_id, vote): return self._request("POST", f"devrant/rants/{rant_id}/vote", body={"vote": vote}) def comment(self, rant_id, text): return self._request("POST", f"devrant/rants/{rant_id}/comments", body={"comment": text}) api = DevRant("https://devplace.net", "USERNAME", "PASSWORD") api.login() print(api.post_rant("Hello from Python", "python,devrant")) ``` ### JavaScript client (drop-in) ```javascript export class DevRant { constructor(baseUrl, username, password) { this.baseUrl = baseUrl.replace(/\/$/, ""); this.username = username; this.password = password; this.auth = {}; } async _request(method, path, params = {}, body = null) { const merged = { ...params, ...this.auth }; const headers = { Accept: "application/json" }; let url = new URL(`${this.baseUrl}/api/${path.replace(/^\//, "")}`); const init = { method, headers }; if (method === "GET" || method === "DELETE") { for (const [k, v] of Object.entries(merged)) url.searchParams.set(k, v); } else { headers["Content-Type"] = "application/x-www-form-urlencoded"; init.body = new URLSearchParams({ ...merged, ...(body || {}) }).toString(); } return (await fetch(url, init)).json(); } async login() { const out = await this._request("POST", "users/auth-token", {}, { username: this.username, password: this.password }); if (!out.success) throw new Error(out.error || "login failed"); const t = out.auth_token; this.auth = { user_id: t.user_id, token_id: t.id, token_key: t.key }; return this.auth; } async rants(sort = "recent", limit = 20, skip = 0) { return (await this._request("GET", "devrant/rants", { sort, limit, skip })).rants || []; } postRant(text, tags = "") { return this._request("POST", "devrant/rants", {}, { rant: text, tags }); } voteRant(rantId, vote) { return this._request("POST", `devrant/rants/${rantId}/vote`, {}, { vote }); } comment(rantId, text) { return this._request("POST", `devrant/rants/${rantId}/comments`, {}, { comment: text }); } } const api = new DevRant("https://devplace.net", "USERNAME", "PASSWORD"); await api.login(); console.log(await api.postRant("Hello from Node", "javascript,devrant")); ``` ### Example scripts in the repository `examples/devrant/` contains the complete clients and runnable scripts: | File | Language | What it does | |------|----------|--------------| | `client.py` / `client.mjs` | Python / JS | Full reusable client (every endpoint). | | `post_rant.py` / `post_rant.mjs` | Python / JS | Post one rant from the command line. | | `feed_watch.py` / `feed_watch.mjs` | Python / JS | Live feed ticker, optional keyword auto-upvote. | | `smoke_test.py` / `smoke_test.mjs` | Python / JS | End-to-end conformance test, prints PASS/FAIL. | They read `DEVRANT_BASE`, `DEVRANT_USERNAME`, and `DEVRANT_PASSWORD` from the environment. ```bash # post a rant DEVRANT_USERNAME=you DEVRANT_PASSWORD=secret6 \ python examples/devrant/post_rant.py "Posted from a script" "python" # run the full conformance test against a running server DEVRANT_BASE=https://devplace.net python examples/devrant/smoke_test.py DEVRANT_BASE=https://devplace.net node examples/devrant/smoke_test.mjs ```