openapi: 3.0.0 info: title: NodeBB Write API description: >- # Overview The following document outlines every route exposed by the NodeBB Write API. As of NodeBB v1.15.0, NodeBB will use these routes to make changes to the database (e.g. creating new posts, editing user profiles, etc.) We invite you to build external integrations with NodeBB using this document as a guide. # History Up until v1.15.0, NodeBB utilised the [WebSocket](https://en.wikipedia.org/wiki/WebSocket) protocol to communicate with the backend. However, it was decided in early 2020 that this usage of WebSocket – while functional – led to occasional wheel reinvention and disregarded an otherwise fully-featured technology (that is, REST). Years prior to this determination, many users of NodeBB had asked for a RESTful API to call against NodeBB, which led to the creation of [`nodebb-plugin-write-api`](https://github.com/NodeBB/nodebb-plugin-write-api). In tandem with the above decision, the Write API was merged into NodeBB core in late 2020. v3 of the Write API (this document) achieves rough feature parity with v2 of the Write API plugin. version: 1.15.0 contact: email: support@nodebb.org license: name: GPL-3.0 servers: - url: /api/v1 tags: - name: users description: 'Account related calls (create, modify, delete, etc.)' - name: categories description: Administrative calls to manage categories paths: /users/: post: tags: - users summary: create a user description: This operation creates a new user account requestBody: required: true content: application/json: schema: type: object properties: username: type: string description: 'If the username is taken, a number will be appended' password: type: string email: type: string required: - username example: username: Dragon Fruit password: s3cre7password email: dragonfruit@example.org responses: '200': description: user successfully created content: application/json: schema: type: object properties: status: $ref: '#/components/schemas/Status' response: $ref: '#/components/schemas/UserObj' '400': $ref: '#/components/responses/400' '401': $ref: '#/components/responses/401' '403': $ref: '#/components/responses/403' '426': $ref: '#/components/responses/426' '500': $ref: '#/components/responses/500' delete: tags: - users summary: delete one or more users description: This operation deletes one or many user accounts, including their contributions (posts, topics, etc.) requestBody: required: true content: application/json: schema: type: object properties: uids: type: array description: A collection of uids items: type: number example: uids: - 1 - 2 - 3 responses: '200': description: user account(s) deleted content: application/json: schema: type: object properties: status: $ref: '#/components/schemas/Status' response: type: object '/users/{uid}': delete: tags: - users summary: delete a single user account parameters: - in: path name: uid schema: type: integer required: true description: uid of the user to delete responses: '200': description: user account deleted content: application/json: schema: type: object properties: status: $ref: '#/components/schemas/Status' response: type: object put: tags: - users summary: update a user account parameters: - in: path name: uid schema: type: integer required: true description: uid of the user to update requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/UserRequest' responses: '200': description: user profile updated content: application/json: schema: type: object properties: status: $ref: '#/components/schemas/Status' response: $ref: '#/components/schemas/UserObj' '401': $ref: '#/components/responses/401' '403': $ref: '#/components/responses/403' '426': $ref: '#/components/responses/426' '500': $ref: '#/components/responses/500' '/users/{uid}/password': put: tags: - users summary: change a user's password parameters: - in: path name: uid schema: type: integer required: true description: uid of the user to update requestBody: required: true content: application/json: schema: type: object properties: currentPassword: type: string description: test example: oldp455word newPassword: type: string example: s3cre7password required: - newPassword responses: '200': description: user profile updated content: application/json: schema: type: object properties: status: $ref: '#/components/schemas/Status' response: type: object '/users/{uid}/follow': post: tags: - users summary: follow a user parameters: - in: path name: uid schema: type: integer required: true description: uid of the user to follow responses: '200': description: successfully followed user content: application/json: schema: type: object properties: status: $ref: '#/components/schemas/Status' response: type: object delete: tags: - users summary: unfollows a user parameters: - in: path name: uid schema: type: integer required: true description: uid of the user to unfollow responses: '200': description: successfully unfollowed user content: application/json: schema: type: object properties: status: $ref: '#/components/schemas/Status' response: type: object '/users/{uid}/ban': put: tags: - users summary: ban a user parameters: - in: path name: uid schema: type: integer required: true description: uid of the user to ban requestBody: content: application/json: schema: type: object properties: until: type: number description: UNIX timestamp of the ban expiry example: 1585775608076 reason: type: string example: the reason for the ban responses: '200': description: successfully banned user content: application/json: schema: type: object properties: status: $ref: '#/components/schemas/Status' response: type: object delete: tags: - users summary: unbans a user parameters: - in: path name: uid schema: type: integer required: true description: uid of the user to unban responses: '200': description: successfully unbanned user content: application/json: schema: type: object properties: status: $ref: '#/components/schemas/Status' response: type: object '/users/{uid}/tokens': post: tags: - users summary: generate a user token description: This route can only be used to generate tokens for the same user. In other words, you cannot use this route to generate a token for a different user than the one you are authenticated as. responses: '200': description: successfully generated a user token content: application/json: schema: type: object properties: status: $ref: '#/components/schemas/Status' response: type: object delete: tags: - users summary: delete user token parameters: - in: path name: token schema: type: string required: true description: a valid API token responses: '200': description: successfully deleted user token content: application/json: schema: type: object properties: status: $ref: '#/components/schemas/Status' response: type: object /categories/: post: tags: - categories summary: create a category description: This operation creates a new category requestBody: required: true content: application/json: schema: type: object properties: name: type: string description: type: string parentCid: type: number cloneFromCid: type: number icon: type: string description: A ForkAwesome icon without the `fa-` prefix bgColor: type: string color: type: string link: type: string class: type: string backgroundImage: type: string required: - name example: name: My New Category description: Lorem ipsum, dolor sit amet parentCid: 0 cloneFromCid: 0 icon: bullhorn bgColor: '#ffffff' color: '#000000' link: 'https://example.org' class: 'col-md-3 col-xs-6' backgroundImage: '/assets/relative/path/to/image' responses: '200': description: category successfully created content: application/json: schema: type: object properties: status: $ref: '#/components/schemas/Status' response: $ref: '#/components/schemas/CategoryObj' /groups/: post: tags: - groups summary: create a new group description: This operation creates a new group requestBody: required: true content: application/json: schema: type: object properties: name: type: string timestamp: type: number disableJoinRequests: type: number enum: [0, 1] disableLeave: type: number enum: [0, 1] hidden: type: number enum: [0, 1] ownerUid: type: number private: type: number enum: [0, 1] description: type: string userTitleEnabled: type: number enum: [0, 1] createtime: type: number required: - name example: name: 'My Test Group' hidden: 1 responses: '200': description: group successfully created content: application/json: schema: type: object properties: status: $ref: '#/components/schemas/Status' response: $ref: components/schemas/GroupObject.yaml#/GroupDataObject delete: tags: - groups summary: Delete an existing group description: This operation deletes an existing group, all members within this group will cease to be members after the group is deleted. responses: '200': description: group successfully deleted content: application/json: schema: type: object properties: status: $ref: '#/components/schemas/Status' response: type: object properties: {} /groups/{slug}/membership/{uid}: put: tags: - groups summary: join a group description: This operation joins an existing group, or causes another user to join a group. If the group is private and you are not an administrator, this method will cause that user to request membership, instead. For user _invitations_, you'll want to call `PUT /groups/{slug}/invites/{uid}`. responses: '200': description: group successfully joined, or membership requested content: application/json: schema: type: object properties: status: $ref: '#/components/schemas/Status' response: type: object properties: {} /topics: post: tags: - topics summary: create a new topic description: This operation creates a new topic with a post. Topic creation without a post is not allowed via the Write API as it is an internal-only method. requestBody: required: true content: application/json: schema: type: object properties: cid: type: number title: type: string content: type: string tags: type: array items: type: string required: - cid - title - content example: cid: 1 title: Test topic content: This is the test topic's content responses: '200': description: topic successfully created content: application/json: schema: type: object properties: status: $ref: '#/components/schemas/Status' response: $ref: components/schemas/TopicObject.yaml#/TopicObject /topics/{tid}: post: tags: - topics summary: peply to a topic description: This operation creates a new reply to an existing topic. requestBody: required: true content: application/json: schema: type: object properties: content: type: string timestamp: type: number toPid: type: number required: - content example: content: This is a test reply responses: '200': description: post successfully created content: application/json: schema: type: object properties: status: $ref: '#/components/schemas/Status' response: $ref: components/schemas/PostsObject.yaml#/PostsObject delete: tags: - topics summary: delete a topic description: This operation purges a topic and all of its posts (careful, there is no confirmation!) responses: '200': description: Topic successfully purged content: application/json: schema: type: object properties: status: $ref: '#/components/schemas/Status' response: type: object properties: {} /topics/{tid}/state: delete: tags: - topics summary: delete a topic description: This operation deletes an existing topic. responses: '200': description: Topic successfully deleted content: application/json: schema: type: object properties: status: $ref: '#/components/schemas/Status' response: type: object properties: {} put: tags: - topics summary: restore a topic description: This operation restores a topic. responses: '200': description: Topic successfully restored content: application/json: schema: type: object properties: status: $ref: '#/components/schemas/Status' response: type: object properties: {} /topics/{tid}/lock: put: tags: - topics summary: lock a topic description: This operation locks an existing topic. responses: '200': description: Topic successfully locked content: application/json: schema: type: object properties: status: $ref: '#/components/schemas/Status' response: type: object properties: {} delete: tags: - topics summary: unlock a topic description: This operation unlocks a topic. responses: '200': description: Topic successfully unlocked content: application/json: schema: type: object properties: status: $ref: '#/components/schemas/Status' response: type: object properties: {} /topics/{tid}/pin: put: tags: - topics summary: pin a topic description: This operation pins an existing topic. responses: '200': description: Topic successfully pinned content: application/json: schema: type: object properties: status: $ref: '#/components/schemas/Status' response: type: object properties: {} delete: tags: - topics summary: unpin a topic description: This operation unpins a topic. responses: '200': description: Topic successfully unpinned content: application/json: schema: type: object properties: status: $ref: '#/components/schemas/Status' response: type: object properties: {} /topics/{tid}/follow: put: tags: - topics summary: follow a topic description: This operation follows (or watches) a topic. responses: '200': description: Topic successfully followed content: application/json: schema: type: object properties: status: $ref: '#/components/schemas/Status' response: type: object properties: {} delete: tags: - topics summary: unfollow a topic description: This operation unfollows (or unwatches) a topic. responses: '200': description: Topic successfully unwatched content: application/json: schema: type: object properties: status: $ref: '#/components/schemas/Status' response: type: object properties: {} /topics/{tid}/ignore: put: tags: - topics summary: ignore a topic description: This operation ignores (or watches) a topic. responses: '200': description: Topic successfully ignored content: application/json: schema: type: object properties: status: $ref: '#/components/schemas/Status' response: type: object properties: {} delete: tags: - topics summary: unignore a topic description: This operation unignores (or unfollows/unwatches) a topic. It is functionally identical to `DEL /topics/{tid}/follow`. responses: '200': description: Topic successfully unignored/unwatched content: application/json: schema: type: object properties: status: $ref: '#/components/schemas/Status' response: type: object properties: {} /topics/{tid}/tags: put: tags: - topics summary: adds tags to a topic description: This operation adds tags to a topic requestBody: required: true content: application/json: schema: type: object properties: tags: type: array description: 'An array of tags' items: type: string example: tags: - test - foobar responses: '200': description: Topic tags successfully added content: application/json: schema: type: object properties: status: $ref: '#/components/schemas/Status' response: type: object properties: {} delete: tags: - topics summary: Removes all tags from a topic description: This operation removed all tags associated with a topic. responses: '200': description: Topic tags successfully removed. content: application/json: schema: type: object properties: status: $ref: '#/components/schemas/Status' response: type: object properties: {} /posts/{pid}: put: tags: - posts summary: edit a post description: This operation edits a post requestBody: required: true content: application/json: schema: type: object properties: content: type: string description: New post content title: type: string description: Topic title, only accepted for main posts required: - content example: content: 'New post content' title: 'New title' responses: '200': description: Post successfully edited content: application/json: schema: type: object properties: status: $ref: '#/components/schemas/Status' response: $ref: components/schemas/PostsObject.yaml#/PostsObject delete: tags: - posts summary: purge a post description: This operation purges a post. responses: '200': description: Post successfully purged content: application/json: schema: type: object properties: status: $ref: '#/components/schemas/Status' response: type: object properties: {} /posts/{pid}/state: put: tags: - posts summary: restore a post description: This operation restores a post. responses: '200': description: Topic successfully restored content: application/json: schema: type: object properties: status: $ref: '#/components/schemas/Status' response: type: object properties: {} delete: tags: - posts summary: deletes a post description: This operation soft deletes a post. responses: '200': description: Post successfully deleted content: application/json: schema: type: object properties: status: $ref: '#/components/schemas/Status' response: type: object properties: {} /posts/{pid}/vote: put: tags: - posts summary: vote on a post description: This operation casts a vote on a post. requestBody: required: true content: application/json: schema: type: object properties: delta: type: number description: Positive integer for upvote, negative integer for downvote (0 to unvote.) example: delta: 1 responses: '200': description: Post successfully upvoted content: application/json: schema: type: object properties: status: $ref: '#/components/schemas/Status' response: type: object properties: {} delete: tags: - posts summary: unvote a post description: This operation removes a pre-cast vote on a post. responses: '200': description: Post successfully unvoted content: application/json: schema: type: object properties: status: $ref: '#/components/schemas/Status' response: type: object properties: {} /posts/{pid}/bookmark: put: tags: - posts summary: bookmark a post description: This operation bookmarks a post. responses: '200': description: Post successfully bookmarked content: application/json: schema: type: object properties: status: $ref: '#/components/schemas/Status' response: type: object properties: {} delete: tags: - posts summary: unbookmark a post description: This operation unbookmarks a post. responses: '200': description: Post successfully unbookmarked content: application/json: schema: type: object properties: status: $ref: '#/components/schemas/Status' response: type: object properties: {} /admin/settings/{setting}: put: tags: - admin summary: update configuration setting description: This operation updates a configuration setting in the backend. The calling user must have the `admin:settings` privilege (or be a superadmin) in order for this call to proceed. parameters: - in: path name: setting schema: type: string required: true description: backend id of the setting to update responses: '200': description: Admin setting updated content: application/json: schema: type: object properties: status: $ref: '#/components/schemas/Status' response: type: object properties: {} components: schemas: Status: type: object properties: code: type: string example: ok message: type: string example: OK Error: type: object properties: status: type: object properties: code: type: string message: type: string response: type: object UserObj: properties: uid: type: number example: 1 username: type: string example: Dragon Fruit userslug: type: string example: dragon-fruit email: type: string example: dragonfruit@example.org 'email:confirmed': type: number example: 1 joindate: type: number example: 1585337827953 lastonline: type: number example: 1585337827953 picture: type: string example: 'https://images.unsplash.com/photo-1560070094-e1f2ddec4337?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=256&h=256&q=80' fullname: type: string example: Mr. Dragon Fruit Jr. location: type: string example: 'Toronto, Canada' birthday: type: string description: A birthdate given in an ISO format parseable by the Date object example: 03/27/2020 website: type: string example: 'https://example.org' aboutme: type: string example: | This is a paragraph all about how my life got twist-turned upside-down and I'd like to take a minute and sit right here, to tell you all about how I because the administrator of NodeBB signature: type: string example: | This is an example signature It can span multiple lines. uploadedpicture: type: string example: /assets/profile/1-profileimg.png description: 'In almost all cases, defer to "picture" instead. Use this if you need to specifically reference the picture uploaded to the forum.' profileviews: type: number example: 1000 reputation: type: number example: 100 postcount: type: number example: 1000 topiccount: type: number example: 50 lastposttime: type: number example: 1585337827953 banned: type: number example: 0 'banned:expire': type: number example: 1585337827953 status: type: string example: online flags: type: number example: 0 followercount: type: number example: 2 followingcount: type: number example: 5 'cover:url': type: string example: /assets/profile/1-cover.png 'cover:position': type: string example: 50.0301% 19.2464% groupTitle: type: string example: '["administrators","Staff"]' groupTitleArray: type: array example: - administrators - Staff 'icon:text': type: string example: D 'icon:bgColor': type: string example: '#9c27b0' joindateISO: type: string example: '2020-03-27T20:30:36.590Z' lastonlineISO: type: string example: '2020-03-27T20:30:36.590Z' banned_until: type: number example: 0 banned_until_readable: type: string example: Not Banned UserRequest: properties: username: type: string example: Dragon Fruit email: type: string example: dragonfruit@example.org fullname: type: string example: Mr. Dragon Fruit Jr. website: type: string example: 'https://example.org' location: type: string example: 'Toronto, Canada' groupTitle: type: string example: '["administrators","Staff"]' birthday: type: string description: A birthdate given in an ISO format parseable by the Date object example: 03/27/2020 signature: type: string example: | This is an example signature It can span multiple lines. aboutme: type: string example: | This is a paragraph all about how my life got twist-turned upside-down and I'd like to take a minute and sit right here, to tell you all about how I because the administrator of NodeBB CategoryObj: properties: cid: type: number example: 1 name: type: string example: My New Category description: type: string example: Lorem ipsum, dolor sit amet descriptionParsed: type: string example: Lorem ipsum, dolor sit amet icon: type: string example: bullhorn bgColor: type: string example: '#ffffff' color: type: string example: '#000000' slug: type: string example: 1/my-new-category parentCid: type: number example: 0 topic_count: type: number example: 0 post_count: type: number example: 0 disabled: type: number example: 0 order: type: number example: 5 link: type: number example: 'https://example.org' numRecentReplies: type: number example: 1 class: type: string example: col-md-3 col-xs-6 imageClass: type: string example: cover isSection: type: number example: 0 totalPostCount: type: number example: 0 totalTopicCount: type: number example: 0 tagWhitelist: type: array example: - some-tag - another-tag unread-class: type: string backgroundImage: type: string example: '/assets/images/covers/Circuit1.png' responses: '400': description: Bad Request content: application/json: schema: $ref: '#/components/schemas/Error' '401': description: Not Authorized content: application/json: schema: $ref: '#/components/schemas/Error' '403': description: Forbidden content: application/json: schema: $ref: '#/components/schemas/Error' '404': description: Not Found content: application/json: schema: $ref: '#/components/schemas/Error' '426': description: Upgrade Required content: application/json: schema: $ref: '#/components/schemas/Error' '500': description: Internal Server Error content: application/json: schema: $ref: '#/components/schemas/Error'