{ "openapi": "3.0.3", "info": { "title": "Forem API V1", "version": "1.0.0", "description": "Access Forem articles, users and other resources via API.\n For a real-world example of Forem in action, check out [DEV](https://www.dev.to).\n All endpoints can be accessed with the 'api-key' header and a accept header, but\n some of them are accessible publicly without authentication.\n\n Dates and date times, unless otherwise specified, must be in\n the [RFC 3339](https://tools.ietf.org/html/rfc3339) format." }, "paths": { "/api/agent_sessions": { "get": { "summary": "list the authenticated user's agent sessions", "tags": [ "agent_sessions" ], "description": "Retrieve a list of the authenticated user's agent sessions.\n\n### Agent Sessions Overview:\n- Agent sessions represent coding conversation transcripts uploaded from CLI tools (like Claude Code).\n- Used by the developer portal to render interactive walkthroughs or session summaries.\n- Requires authentication.", "operationId": "getAgentSessions", "responses": { "200": { "description": "successful", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/AgentSessionIndex" } } } } }, "401": { "description": "unauthorized" } } }, "post": { "summary": "upload a new agent session", "tags": [ "agent_sessions" ], "description": "Upload a new agent session.\n\n### S3 Upload Workflow:\n1. Call the S3 presign endpoint to obtain a direct upload URL for the raw session transcript file.\n2. Upload the raw transcript to S3.\n3. Send a POST request to this endpoint with the S3 key (`s3_key`) and the pre-parsed, curated JSON payload (`curated_data`).", "operationId": "createAgentSession", "parameters": [ ], "responses": { "201": { "description": "created", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AgentSessionIndex" } } } }, "401": { "description": "unauthorized" }, "422": { "description": "unprocessable" } }, "requestBody": { "content": { "application/json": { "schema": { "type": "object", "properties": { "title": { "type": "string", "description": "Title for the session (auto-generated if omitted)" }, "curated_data": { "type": "string", "description": "JSON string of curated session data with messages array and metadata." }, "s3_key": { "type": "string", "description": "S3 object key from presign endpoint (optional)." }, "tool_name": { "type": "string", "description": "Tool that produced the session (e.g. claude_code, codex).", "enum": [ "claude_code", "codex", "gemini_cli", "github_copilot", "opencode", "pi" ] } }, "required": [ "curated_data" ] } } }, "description": "Agent session upload parameters." } } }, "/api/agent_sessions/{id}": { "get": { "summary": "show details for an agent session", "tags": [ "agent_sessions" ], "description": "Retrieve details for a single agent session by unique slug or ID.\n\n### Integration Tip:\n- Returns the complete session structure including parsed message logs, token counts, slices, and tool execution metadata.", "operationId": "getAgentSessionById", "parameters": [ { "name": "id", "in": "path", "required": true, "description": "The unique slug or ID of the agent session.", "schema": { "type": "string" }, "example": "my-session-abc123" } ], "responses": { "200": { "description": "successful", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/AgentSessionShow" } } } }, "401": { "description": "unauthorized" }, "404": { "description": "not found" } } } }, "/api/analytics/totals": { "get": { "summary": "Retrieve analytics totals", "tags": [ "analytics" ], "description": "Retrieve aggregated lifetime stats (views, reactions, comments) for articles.\n\n### Scope Control:\n- Specify `article_id` to query a single post's metrics.\n- Specify `organization_id` to retrieve metrics across all articles owned by the target organization.", "parameters": [ { "name": "article_id", "in": "query", "required": false, "description": "Optional ID to limit totals to a single article.", "schema": { "type": "integer" } }, { "name": "organization_id", "in": "query", "required": false, "description": "Optional ID to limit totals to an organization's articles.", "schema": { "type": "integer" } } ], "responses": { "200": { "description": "successful" } } } }, "/api/analytics/historical": { "get": { "summary": "Retrieve historical analytics", "tags": [ "analytics" ], "description": "Retrieve historical analytics data graphed over a time range.\n\n### Time Range Formats:\n- **start**: Start date (e.g. `2024-01-01`). Required.\n- **end**: End date (e.g. `2024-01-31`). Defaults to current date if omitted.", "parameters": [ { "name": "start", "in": "query", "required": true, "description": "Start date (YYYY-MM-DD format).", "schema": { "type": "string" }, "example": "2024-01-01" }, { "name": "end", "in": "query", "required": false, "description": "End date (YYYY-MM-DD format).", "schema": { "type": "string" }, "example": "2024-01-31" }, { "name": "article_id", "in": "query", "required": false, "description": "Limit stats to a single article.", "schema": { "type": "integer" } }, { "name": "organization_id", "in": "query", "required": false, "description": "Limit stats to an organization's articles.", "schema": { "type": "integer" } } ], "responses": { "200": { "description": "successful" } } } }, "/api/analytics/past_day": { "get": { "summary": "Retrieve analytics for the past day", "tags": [ "analytics" ], "description": "Retrieve real-time hourly analytics statistics for the last 24 hours. Used for live graphs.", "parameters": [ { "name": "article_id", "in": "query", "required": false, "schema": { "type": "integer" } }, { "name": "organization_id", "in": "query", "required": false, "schema": { "type": "integer" } } ], "responses": { "200": { "description": "successful" } } } }, "/api/analytics/referrers": { "get": { "summary": "Retrieve referrer analytics", "tags": [ "analytics" ], "description": "Retrieve traffic referring domains and URL source tracking metrics for articles.", "parameters": [ { "name": "start", "in": "query", "required": false, "schema": { "type": "string" }, "example": "2024-01-01" }, { "name": "end", "in": "query", "required": false, "schema": { "type": "string" }, "example": "2024-01-31" }, { "name": "article_id", "in": "query", "required": false, "schema": { "type": "integer" } }, { "name": "organization_id", "in": "query", "required": false, "schema": { "type": "integer" } } ], "responses": { "200": { "description": "successful" } } } }, "/api/analytics/top_contributors": { "get": { "summary": "Retrieve top contributors analytics", "tags": [ "analytics" ], "description": "Retrieve top organization contributors ordered by article engagement scores.", "parameters": [ { "name": "start", "in": "query", "required": false, "schema": { "type": "string" }, "example": "2024-01-01" }, { "name": "end", "in": "query", "required": false, "schema": { "type": "string" }, "example": "2024-01-31" }, { "name": "article_id", "in": "query", "required": false, "schema": { "type": "integer" } }, { "name": "organization_id", "in": "query", "required": false, "schema": { "type": "integer" } } ], "responses": { "200": { "description": "successful" } } } }, "/api/analytics/follower_engagement": { "get": { "summary": "Retrieve follower engagement analytics", "tags": [ "analytics" ], "description": "Retrieve stats detailing new follower growth and engagement over time.", "parameters": [ { "name": "start", "in": "query", "required": false, "schema": { "type": "string" }, "example": "2024-01-01" }, { "name": "end", "in": "query", "required": false, "schema": { "type": "string" }, "example": "2024-01-31" } ], "responses": { "200": { "description": "successful" } } } }, "/api/analytics/dashboard": { "get": { "summary": "Retrieve dashboard analytics bundle", "tags": [ "analytics" ], "description": "Retrieve a complete bundled metrics package (totals, history, top posts) for rendering dashboard landing pages.", "parameters": [ { "name": "start", "in": "query", "required": false, "schema": { "type": "string" }, "example": "2024-01-01" }, { "name": "end", "in": "query", "required": false, "schema": { "type": "string" }, "example": "2024-01-31" }, { "name": "article_id", "in": "query", "required": false, "schema": { "type": "integer" } }, { "name": "organization_id", "in": "query", "required": false, "schema": { "type": "integer" } } ], "responses": { "200": { "description": "successful" } } } }, "/api/analytics/heatmap": { "get": { "summary": "Retrieve heatmap activity", "tags": [ "analytics" ], "description": "Retrieve user activity heatmap metrics (commits, posts, reactions) grouped by weekdays and hours.", "parameters": [ { "name": "end", "in": "query", "required": false, "schema": { "type": "string" }, "example": "2024-12-31" } ], "responses": { "200": { "description": "successful" } } } }, "/api/articles": { "post": { "summary": "Publish article", "tags": [ "articles" ], "description": "This endpoint allows the client to create a new article.\n\n\"Articles\" are all the posts that users create on DEV/Forem that typically show up in the feed. They can be a blog post, a discussion question, a help thread etc. but are referred to as articles within the code.\n\n### Body Parameter Tips:\n- **title**: A compelling and descriptive title for the article.\n- **body_markdown**: The main text of the article in Markdown format. You can use standard Markdown as well as Forem-specific Liquid tags (e.g., `{% embed ... %}`). You can also include YAML front matter at the very beginning of the markdown to specify metadata such as tags, series, and cover image.\n- **published**: Set to `true` to immediately publish the article and make it visible in feeds. Set to `false` (default) to save it as a draft.\n- **tags**: A comma-separated list of tags (up to 4 tags). Tags help categorize your post and improve discoverability.\n- **series**: Group articles together by specifying a series name. If the series does not exist, it will be created.\n- **main_image**: Absolute URL of the cover image for the article.\n- **canonical_url**: If this post was originally published elsewhere, specify the canonical URL to maintain SEO integrity.\n- **description**: A short summary of the article used for previews and SEO meta description.", "operationId": "createArticle", "parameters": [ ], "responses": { "201": { "description": "An Article" }, "401": { "description": "Unauthorized" }, "422": { "description": "Unprocessable Entity" } }, "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Article" } } }, "description": "Representation of Article to be created" } }, "get": { "summary": "Published articles", "security": [ ], "tags": [ "articles" ], "description": "This endpoint allows the client to retrieve a list of articles.\n\n\"Articles\" are all the posts that users create on DEV/Forem that typically show up in the feed. They can be a blog post, a discussion question, a help thread etc. but are referred to as articles within the code.\n\nBy default it will return featured, published articles ordered by descending popularity.\n\nIt supports pagination, each page will contain `30` articles by default.\n\n### Query Parameter Usage Tips:\n- **Filtering by Tags**:\n - Use `tag` to filter articles containing a single exact tag (e.g. `tag=discuss`).\n - Use `tags` to retrieve articles containing *any* of the comma-separated list of tags (e.g. `tags=javascript,css`).\n - Use `tags_exclude` to filter out articles containing any of the comma-separated list of tags (e.g. `tags_exclude=node,java`).\n- **Filtering by User / Organization**:\n - Use `username` to retrieve articles belonging to a specific User or Organization. Articles are returned in reverse chronological publication order.\n- **State Options**:\n - Use `state=fresh` to fetch fresh articles.\n - Use `state=rising` to fetch rising/trending articles.\n - Combine `state=all` with `username` to fetch up to `1000` articles (both published and unpublished) from that user/organization in a single page.\n- **Top / Popularity**:\n - Use `top=N` to return the most popular articles published in the last `N` days (e.g. `top=7` for top articles of the week, `top=30` for top of the month). This parameter can be combined with `tag` to find top articles in a specific niche.\n- **Collections**:\n - Use `collection_id` to retrieve articles belonging to a specific collection/series, sorted chronologically.", "operationId": "getArticles", "parameters": [ { "$ref": "#/components/parameters/pageParam" }, { "$ref": "#/components/parameters/perPageParam30to1000" }, { "name": "tag", "in": "query", "required": false, "description": "Using this parameter will retrieve articles that contain the requested tag. Articles\nwill be ordered by descending popularity.This parameter can be used in conjuction with `top`.", "schema": { "type": "string" }, "example": "discuss" }, { "name": "tags", "in": "query", "required": false, "description": "Using this parameter will retrieve articles with any of the comma-separated tags.\nArticles will be ordered by descending popularity.", "schema": { "type": "string" }, "example": "javascript, css" }, { "name": "tags_exclude", "in": "query", "required": false, "description": "Using this parameter will retrieve articles that do _not_ contain _any_\nof comma-separated tags. Articles will be ordered by descending popularity.", "schema": { "type": "string" }, "example": "node, java" }, { "name": "username", "in": "query", "required": false, "description": "Using this parameter will retrieve articles belonging\n to a User or Organization ordered by descending publication date.\n If `state=all` the number of items returned will be `1000` instead of the default `30`.\n This parameter can be used in conjuction with `state`.", "schema": { "type": "string" }, "example": "ben" }, { "name": "state", "in": "query", "required": false, "description": "Using this parameter will allow the client to check which articles are fresh or rising.\n If `state=fresh` the server will return fresh articles.\n If `state=rising` the server will return rising articles.\n This param can be used in conjuction with `username`, only if set to `all`.", "schema": { "type": "string", "enum": [ "fresh", "rising", "all" ] }, "example": "fresh" }, { "name": "top", "in": "query", "required": false, "description": "Using this parameter will allow the client to return the most popular articles\nin the last `N` days.\n`top` indicates the number of days since publication of the articles returned.\nThis param can be used in conjuction with `tag`.", "schema": { "type": "integer", "format": "int32", "minimum": 1 }, "example": 2 }, { "name": "collection_id", "in": "query", "required": false, "description": "Adding this will allow the client to return the list of articles\nbelonging to the requested collection, ordered by ascending publication date.", "schema": { "type": "integer", "format": "int32" }, "example": 99 } ], "responses": { "200": { "description": "A List of Articles", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/ArticleIndex" } } } } } } } }, "/api/articles/search": { "get": { "summary": "Search for articles", "security": [ ], "tags": [ "articles" ], "description": "This endpoint allows the client to search for articles.\n\n### Search Guidance:\n- **q**: The search query term. Matches against article titles, tags, and body content.\n- **top**: Restricts search results to articles published within the last `N` days.\n- **page** & **per_page**: Standard pagination support. Use this to display results sequentially on a search results page.", "operationId": "searchArticles", "parameters": [ { "name": "q", "in": "query", "required": false, "description": "The search query term. Matches against article titles, tags, and body content.", "schema": { "type": "string" } }, { "name": "top", "in": "query", "required": false, "description": "Restricts search results to articles published within the last `N` days.", "schema": { "type": "integer" } }, { "name": "page", "in": "query", "required": false, "description": "Pagination page index.", "schema": { "type": "integer" } }, { "name": "per_page", "in": "query", "required": false, "description": "The number of items to return per page.", "schema": { "type": "integer" } } ], "responses": { "200": { "description": "A List of Articles", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/ArticleIndex" } } } } } } } }, "/api/articles/latest": { "get": { "summary": "Published articles sorted by published date", "security": [ ], "tags": [ "articles" ], "description": "This endpoint allows the client to retrieve a list of articles ordered strictly by descending publication date.\n\n### Usage Tips:\n- Bypasses all Forem/DEV feed personalization and popularity algorithms.\n- Ideal for building RSS feeds, chronological timelines, or \"latest posts\" widgets.\n- Supports standard `page` and `per_page` query parameters.", "operationId": "getLatestArticles", "parameters": [ { "$ref": "#/components/parameters/pageParam" }, { "$ref": "#/components/parameters/perPageParam30to1000" } ], "responses": { "200": { "description": "A List of Articles", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/ArticleIndex" } } } } } } } }, "/api/articles/{id}": { "get": { "summary": "Published article by id", "security": [ ], "tags": [ "articles" ], "description": "This endpoint allows the client to retrieve a single published article given its unique numerical `id`.\n\n### Integration Tip:\n- Returns the complete serialized article object including its HTML/markdown representation, tags, and author profile details.\n- To retrieve an article using its URL path structure instead of its numeric ID, use the `/api/articles/{username}/{slug}` endpoint.", "operationId": "getArticleById", "parameters": [ { "name": "id", "in": "path", "required": true, "description": "The unique numerical ID of the published article.", "schema": { "type": "integer" } } ], "responses": { "200": { "description": "An Article", "content": { "application/json": { "schema": { "type": "object", "items": { "$ref": "#/components/schemas/ArticleIndex" } } } } }, "404": { "description": "Article Not Found" } } }, "put": { "summary": "Update an article by id", "tags": [ "articles" ], "description": "This endpoint allows the client to update an existing article by its unique numerical `id`.\n\n### Authorization Constraints:\n- The API key provided must belong to the author of the article.\n- Supports updating individual fields such as `title`, `body_markdown`, `published`, `tags`, etc.\n- Setting `published: false` on an already published article will revert it to draft status.", "operationId": "updateArticle", "parameters": [ { "name": "id", "in": "path", "required": true, "description": "The unique numerical ID of the article to update.", "schema": { "type": "integer", "format": "int32", "minimum": 1 }, "example": 123 } ], "responses": { "200": { "description": "An Article" }, "404": { "description": "Article Not Found" }, "401": { "description": "Unauthorized" }, "422": { "description": "Unprocessable Entity" } }, "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Article" } } }, "description": "Representation of Article to be updated" } } }, "/api/articles/{username}/{slug}": { "get": { "summary": "Published article by path", "security": [ ], "tags": [ "articles" ], "description": "This endpoint allows the client to retrieve a single published article given its `username` and `slug` (the URL-friendly path identifier).\n\n### Usage Tip:\n- Handy for resolving absolute Forem/DEV article URLs (e.g., `https://dev.to/username/slug`) into their corresponding API data models.", "operationId": "getArticleByPath", "parameters": [ { "name": "username", "in": "path", "required": true, "description": "The username of the article's author or organization.", "schema": { "type": "string" } }, { "name": "slug", "in": "path", "required": true, "description": "The URL-friendly slug of the article (e.g. `my-first-post-1234`).", "schema": { "type": "string" } } ], "responses": { "200": { "description": "An Article", "content": { "application/json": { "schema": { "type": "object", "items": { "$ref": "#/components/schemas/ArticleIndex" } } } } }, "404": { "description": "Article Not Found" } } } }, "/api/articles/me": { "get": { "summary": "User's articles", "tags": [ "articles", "users" ], "description": "This endpoint allows the client to retrieve a list of published articles on behalf of an authenticated user.\n\n\"Articles\" are all the posts that users create on DEV that typically show up in the feed. They can be a blog post, a discussion question, a help thread etc. but is referred to as article within the code.\n\nPublished articles will be in reverse chronological publication order.\n\nIt will return published articles with pagination. By default a page will contain 30 articles.", "operationId": "getUserArticles", "parameters": [ { "$ref": "#/components/parameters/pageParam" }, { "$ref": "#/components/parameters/perPageParam30to1000" } ], "responses": { "401": { "description": "Unauthorized" }, "200": { "description": "A List of the authenticated user's Articles", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/ArticleIndex" } } } } } } } }, "/api/articles/me/published": { "get": { "summary": "User's published articles", "tags": [ "articles", "users" ], "description": "This endpoint allows the client to retrieve a list of published articles on behalf of an authenticated user.\n\n\"Articles\" are all the posts that users create on DEV that typically show up in the feed. They can be a blog post, a discussion question, a help thread etc. but is referred to as article within the code.\n\nPublished articles will be in reverse chronological publication order.\n\nIt will return published articles with pagination. By default a page will contain 30 articles.", "operationId": "getUserPublishedArticles", "parameters": [ { "$ref": "#/components/parameters/pageParam" }, { "$ref": "#/components/parameters/perPageParam30to1000" } ], "responses": { "401": { "description": "Unauthorized" }, "200": { "description": "A List of the authenticated user's Articles", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/ArticleIndex" } } } } } } } }, "/api/articles/me/unpublished": { "get": { "summary": "User's unpublished articles", "tags": [ "articles", "users" ], "description": "This endpoint allows the client to retrieve a list of unpublished articles on behalf of an authenticated user.\n\n\"Articles\" are all the posts that users create on DEV that typically show up in the feed. They can be a blog post, a discussion question, a help thread etc. but is referred to as article within the code.\n\nUnpublished articles will be in reverse chronological creation order.\n\nIt will return unpublished articles with pagination. By default a page will contain 30 articles.", "operationId": "getUserUnpublishedArticles", "parameters": [ { "$ref": "#/components/parameters/pageParam" }, { "$ref": "#/components/parameters/perPageParam30to1000" } ], "responses": { "401": { "description": "Unauthorized" }, "200": { "description": "A List of the authenticated user's Articles", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/ArticleIndex" } } } } } } } }, "/api/articles/me/all": { "get": { "summary": "User's all articles", "tags": [ "articles", "users" ], "description": "This endpoint allows the client to retrieve a list of all articles on behalf of an authenticated user.\n\n\"Articles\" are all the posts that users create on DEV that typically show up in the feed. They can be a blog post, a discussion question, a help thread etc. but is referred to as article within the code.\n\nIt will return both published and unpublished articles with pagination.\n\nUnpublished articles will be at the top of the list in reverse chronological creation order. Published articles will follow in reverse chronological publication order.\n\nBy default a page will contain 30 articles.", "operationId": "getUserAllArticles", "parameters": [ { "$ref": "#/components/parameters/pageParam" }, { "$ref": "#/components/parameters/perPageParam30to1000" } ], "responses": { "401": { "description": "Unauthorized" }, "200": { "description": "A List of the authenticated user's Articles", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/ArticleIndex" } } } } } } } }, "/api/articles/{id}/unpublish": { "put": { "summary": "Unpublish an article", "tags": [ "articles" ], "description": "This endpoint allows the client to unpublish an article.\n\nThe user associated with the API key must have any 'admin' or 'moderator' role.\n\nThe article will be unpublished and will no longer be visible to the public. It will remain\nin the database and will set back to draft status on the author's posts dashboard. Any\nnotifications associated with the article will be deleted. Any comments on the article\nwill remain.", "operationId": "unpublishArticle", "parameters": [ { "name": "id", "in": "path", "required": true, "description": "The ID of the article to unpublish.", "schema": { "type": "integer", "format": "int32", "minimum": 1 }, "example": 1 }, { "name": "note", "in": "query", "required": false, "description": "Content for the note that's created along with unpublishing", "schema": { "type": "string" }, "example": "Admin requested unpublishing all articles via API" } ], "responses": { "204": { "description": "Article successfully unpublished" }, "401": { "description": "Unauthorized" }, "404": { "description": "Article Not Found" } } } }, "/api/articles/semantic_search": { "get": { "summary": "Perform a semantic fuzzy search on articles", "tags": [ "articles" ], "description": "Allows authenticated clients to search articles using Forem's semantic embeddings database.", "parameters": [ { "name": "q", "in": "query", "required": true, "description": "The search query term to match semantically.", "schema": { "type": "string" } }, { "name": "per_page", "in": "query", "required": false, "description": "Limit of articles returned (default 10, max 50).", "schema": { "type": "integer" } }, { "name": "page", "in": "query", "required": false, "description": "Pagination page index.", "schema": { "type": "integer" } }, { "name": "threshold", "in": "query", "required": false, "description": "Optional cosine distance threshold (between 0.0 and 2.0) to filter results.", "schema": { "type": "number" } } ], "responses": { "200": { "description": "successful", "content": { "application/json": { "schema": { "type": "array", "items": { "type": "object", "properties": { "id": { "type": "integer" }, "title": { "type": "string" }, "description": { "type": "string", "nullable": true }, "cover_image": { "type": "string", "nullable": true }, "slug": { "type": "string" }, "path": { "type": "string" }, "url": { "type": "string" }, "comments_count": { "type": "integer" }, "public_reactions_count": { "type": "integer" }, "published_at": { "type": "string" }, "distance": { "type": "number" }, "similarity": { "type": "number" } } } } } } }, "400": { "description": "bad request" }, "401": { "description": "unauthorized" } } } }, "/api/segments": { "get": { "summary": "Manually managed audience segments", "tags": [ "segments" ], "description": "Retrieve a list of manually managed audience segments.\n\n### Audience Segments Overview:\n- Audience Segments are cohorts of users grouped together for targeting announcements, features, or promotional campaign banners (Billboards).\n- This endpoint lists manual cohorts created and maintained by site administrators.\n- Requires administrator privileges.\n\nThe endpoint supports pagination, and each page will contain `30` segments by default.", "operationId": "getSegments", "parameters": [ { "$ref": "#/components/parameters/perPageParam30to1000" } ], "responses": { "200": { "description": "A List of manually managed audience segments", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/Segment" } } } } }, "401": { "description": "Unauthorized" } } }, "post": { "summary": "Create a manually managed audience segment", "tags": [ "segments" ], "description": "Create a new manually managed audience segment.\n\n### Usage Guidance:\n- Used by administrators to define a new target cohort group.\n- Users can be added or removed in bulk later via segment member endpoints.", "operationId": "createSegment", "responses": { "201": { "description": "A manually managed audience segment" }, "401": { "description": "Unauthorized" } } } }, "/api/segments/{id}": { "get": { "summary": "A manually managed audience segment", "tags": [ "segments" ], "description": "Retrieve details of a single manually-managed audience segment specified by ID.\n\n### Integration Tip:\n- Includes segment type (`manual`), configuration, and metadata.\n- Automatic/system-generated segments cannot be queried or updated via this endpoint.", "operationId": "getSegment", "parameters": [ { "name": "id", "in": "path", "required": true, "description": "Unique segment numerical ID.", "schema": { "type": "integer", "format": "int32", "minimum": 1 } } ], "responses": { "200": { "description": "The audience segment", "content": { "application/json": { "schema": { "type": "object", "items": { "$ref": "#/components/schemas/Segment" } } } } }, "401": { "description": "Unauthorized" }, "404": { "description": "Audience Segment Not Found" } } }, "delete": { "summary": "Delete a manually managed audience segment", "tags": [ "segments" ], "description": "Delete an audience segment specified by ID.\n\n### Constraints:\n- Audience segments cannot be deleted if they are currently assigned to any active or pending Billboards.", "operationId": "deleteSegment", "parameters": [ { "name": "id", "in": "path", "required": true, "description": "Unique segment numerical ID.", "schema": { "type": "integer", "format": "int32", "minimum": 1 } } ], "responses": { "200": { "description": "The deleted audience segment" }, "401": { "description": "Unauthorized" }, "404": { "description": "Audience Segment Not Found" }, "409": { "description": "Audience segment could not be deleted" } } } }, "/api/segments/{id}/users": { "get": { "summary": "Users in a manually managed audience segment", "tags": [ "segments" ], "description": "Retrieve a paginated list of users enrolled in the specified manual audience segment.\n\n### Pagination Guidance:\n- Supports standard `page` and `per_page` controls, returning 30 users per page by default.", "operationId": "getUsersInSegment", "parameters": [ { "name": "id", "in": "path", "required": true, "description": "Unique segment numerical ID.", "schema": { "type": "integer", "format": "int32", "minimum": 1 } }, { "$ref": "#/components/parameters/perPageParam30to1000" } ], "responses": { "200": { "description": "A List of users in the audience segment", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/User" } } } } }, "401": { "description": "Unauthorized" }, "404": { "description": "Audience Segment Not Found" } } } }, "/api/segments/{id}/add_users": { "put": { "summary": "Add users to a manually managed audience segment", "tags": [ "segments" ], "description": "Add users in bulk to the specified manual audience segment.\n\n### Bulk Update Behavior:\n- Accepts a JSON array of `user_ids` in the request body.\n- Returns a list of successes and failures. Successful additions include users already present in the segment.", "operationId": "addUsersToSegment", "parameters": [ { "name": "id", "in": "path", "required": true, "description": "Unique segment numerical ID.", "schema": { "type": "integer", "format": "int32", "minimum": 1 } } ], "responses": { "200": { "description": "Result of adding the users to the segment." }, "401": { "description": "Unauthorized" }, "404": { "description": "Audience Segment Not Found" }, "422": { "description": "Unprocessable Entity" } }, "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SegmentUserIds" } } }, "description": "Map containing user IDs to enroll in the segment." } } }, "/api/segments/{id}/remove_users": { "put": { "summary": "Remove users from a manually managed audience segment", "tags": [ "segments" ], "description": "Remove users in bulk from the specified manual audience segment.\n\n### Bulk Update Behavior:\n- Accepts a JSON array of `user_ids` in the request body.\n- Returns successes (users successfully removed) and failures (users who were not members of the segment).", "operationId": "removeUsersFromSegment", "parameters": [ { "name": "id", "in": "path", "required": true, "description": "Unique segment numerical ID.", "schema": { "type": "integer", "format": "int32", "minimum": 1 } } ], "responses": { "200": { "description": "Result of removing the users to the segment." }, "401": { "description": "Unauthorized" }, "404": { "description": "Audience Segment Not Found" }, "422": { "description": "Unprocessable Entity" } }, "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SegmentUserIds" } } }, "description": "Map containing user IDs to remove from the segment." } } }, "/api/badge_achievements": { "get": { "summary": "Retrieve all badge achievements", "tags": [ "badge_achievements" ], "description": "Retrieve a list of all badge achievements (awarded badges) in the system. Requires administrator privileges.", "parameters": [ { "name": "page", "in": "query", "required": false, "description": "Pagination page index.", "schema": { "type": "integer" } } ], "responses": { "200": { "description": "successful", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/BadgeAchievement" } } } } } } }, "post": { "summary": "Create a badge achievement", "tags": [ "badge_achievements" ], "description": "Award a badge to a user. Requires administrator privileges.\n\n### Integration Tips:\n- **user_id**: The numeric ID of the user receiving the badge.\n- **badge_id**: The numeric ID of the badge being awarded.\n- **rewarding_context_message_markdown**: Optional personalized message shown in the notification or profile feed to explain why the user was awarded the badge.", "parameters": [ ], "responses": { "201": { "description": "created", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/BadgeAchievement" } } } } }, "requestBody": { "content": { "application/json": { "schema": { "type": "object", "properties": { "badge_achievement": { "type": "object", "properties": { "user_id": { "type": "integer" }, "badge_id": { "type": "integer" }, "rewarding_context_message_markdown": { "type": "string" }, "include_default_description": { "type": "boolean" } }, "required": [ "user_id", "badge_id" ] } } } } }, "description": "Badge achievement details." } } }, "/api/badge_achievements/{id}": { "get": { "summary": "Retrieve a badge achievement's details", "tags": [ "badge_achievements" ], "description": "Retrieve details of a specific badge award/achievement by ID.", "parameters": [ { "name": "id", "in": "path", "required": true, "description": "Badge achievement unique ID.", "schema": { "type": "integer" } } ], "responses": { "200": { "description": "successful", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/BadgeAchievement" } } } } } }, "delete": { "summary": "Delete a badge achievement", "tags": [ "badge_achievements" ], "description": "Revoke a badge award by deleting the badge achievement. Requires administrator privileges.", "parameters": [ { "name": "id", "in": "path", "required": true, "description": "Badge achievement unique ID to delete.", "schema": { "type": "integer" } } ], "responses": { "204": { "description": "no content" } } } }, "/api/badges": { "get": { "summary": "Retrieve all badges", "tags": [ "badges" ], "description": "Retrieve a list of all badges available on the platform.\n\n### Badges Overview:\n- Badges recognize achievements (e.g., \"Top Writer\", \"Beloved Community Member\", or anniversary milestones).\n- Publicly visible on user profiles.", "parameters": [ { "name": "page", "in": "query", "required": false, "description": "Pagination page index.", "schema": { "type": "integer" } } ], "responses": { "200": { "description": "successful", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/Badge" } } } } } } }, "post": { "summary": "Create a badge", "tags": [ "badges" ], "description": "Create a new badge. Requires administrator privileges.\n\n### Body Parameter Guidelines:\n- **title**: Unique name for the badge.\n- **description**: Text explanation of the achievement.\n- **remote_badge_image_url**: Public URL to an image asset (PNG, GIF, or SVG) representing the badge icon.\n- **allow_multiple_awards**: Set to `true` if a user can earn the same badge multiple times (e.g. weekly challenges).", "parameters": [ ], "responses": { "201": { "description": "created", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Badge" } } } } }, "requestBody": { "content": { "application/json": { "schema": { "type": "object", "properties": { "badge": { "type": "object", "properties": { "title": { "type": "string" }, "description": { "type": "string" }, "remote_badge_image_url": { "type": "string" }, "credits_awarded": { "type": "integer" }, "allow_multiple_awards": { "type": "boolean" } }, "required": [ "title", "description", "remote_badge_image_url" ] } } } } }, "description": "Badge properties to create." } } }, "/api/badges/{id}": { "get": { "summary": "Retrieve a badge's details", "tags": [ "badges" ], "description": "Retrieve details of a single badge by unique numeric ID.", "parameters": [ { "name": "id", "in": "path", "required": true, "description": "Unique badge ID.", "schema": { "type": "integer" } } ], "responses": { "200": { "description": "successful", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Badge" } } } } } }, "patch": { "summary": "Update a badge", "tags": [ "badges" ], "description": "Update badge details (title, description, credits awarded, etc.) by unique ID. Requires administrator privileges.", "parameters": [ { "name": "id", "in": "path", "required": true, "description": "Unique badge ID to update.", "schema": { "type": "integer" } } ], "responses": { "200": { "description": "successful", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Badge" } } } } }, "requestBody": { "content": { "application/json": { "schema": { "type": "object", "properties": { "badge": { "type": "object", "properties": { "title": { "type": "string" }, "description": { "type": "string" }, "credits_awarded": { "type": "integer" }, "allow_multiple_awards": { "type": "boolean" } } } } } } }, "description": "Badge properties to update." } }, "delete": { "summary": "Delete a badge", "tags": [ "badges" ], "description": "Delete a badge configuration from the system by ID. Requires administrator privileges.", "parameters": [ { "name": "id", "in": "path", "required": true, "description": "Unique badge ID to delete.", "schema": { "type": "integer" } } ], "responses": { "204": { "description": "no content" } } } }, "/api/billboards": { "get": { "summary": "Billboards", "tags": [ "billboards" ], "description": "Retrieve a list of all billboards configured in the system.\n\n ### Billboards Overview:\n - Billboards are custom promotional ads, notification banners, or call-to-actions shown on the Forem website.\n - Requires administrative privileges.\n - Returned objects include layout code, scheduling parameters, geo-targeting configurations, and custom target audience segment associations.", "responses": { "200": { "description": "successful", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/Billboard" } } } } }, "401": { "description": "unauthorized" } } }, "post": { "summary": "Create a billboard", "tags": [ "billboards" ], "description": "Create a new billboard.\n\n ### Parameter Options & Tips:\n - **body_markdown**: The HTML/Markdown advertisement copy.\n - **placement_area**: Target region in layouts (e.g. `post_comments` below comments, `sidebar` in sidebars, `home_feed` between posts).\n - **display_to**: Cohort target rules (e.g. `all` for everyone, `logged_in`, `guests`, or customized segments).\n - **target_geolocations**: Comma-separated ISO codes for country/region targeting.\n - **approved** & **published**: Set to `true` to activate billboard rotation instantly.", "parameters": [ ], "responses": { "201": { "description": "A billboard", "content": { "application/json": { "schema": { "type": "object", "items": { "$ref": "#/components/schemas/Billboard" } } } } }, "401": { "description": "unauthorized" }, "422": { "description": "unprocessable" } }, "requestBody": { "content": { "application/json": { "schema": { "type": "object", "items": { "$ref": "#/components/schemas/Billboard" } } } }, "description": "Billboard parameters." } } }, "/api/billboards/{id}": { "get": { "summary": "A billboard (by id)", "tags": [ "billboards" ], "description": "Retrieve full configurations of a single billboard by ID. Requires admin credentials.", "parameters": [ { "name": "id", "in": "path", "required": true, "description": "The ID of the billboard.", "schema": { "type": "integer", "format": "int32", "minimum": 1 }, "example": 123 } ], "responses": { "200": { "description": "successful" }, "401": { "description": "unauthorized" }, "404": { "description": "Unknown Billboard ID" } } }, "put": { "summary": "Update a billboard by ID", "tags": [ "billboards" ], "description": "Update an existing billboard's configurations.\n\n ### Integration Guidance:\n - Allows changing placement area, geolocations, target segments, or text copy.\n - Updating an active billboard takes effect instantly in the layout delivery cache.", "parameters": [ { "name": "id", "in": "path", "required": true, "description": "The ID of the billboard to update.", "schema": { "type": "integer", "format": "int32", "minimum": 1 }, "example": 123 } ], "responses": { "200": { "description": "successful", "content": { "application/json": { "schema": { "type": "object", "items": { "$ref": "#/components/schemas/Billboard" } } } } }, "404": { "description": "not found" }, "401": { "description": "unauthorized" } }, "requestBody": { "content": { "application/json": { "schema": { "type": "object", "items": { "$ref": "#/components/schemas/Billboard" } } } }, "description": "Billboard updated attributes." } } }, "/api/billboards/{id}/unpublish": { "put": { "summary": "Unpublish a billboard", "tags": [ "billboards" ], "description": "Remove a billboard from active rotation by unpublishing it.\n\n ### Usage:\n - Instantly disables display across all pages while keeping the configuration stored in the database for later reactivations or historical reporting.", "parameters": [ { "name": "id", "in": "path", "required": true, "description": "The ID of the billboard to unpublish.", "schema": { "type": "integer", "format": "int32", "minimum": 1 }, "example": 123 } ], "responses": { "204": { "description": "no content" }, "404": { "description": "not found" }, "401": { "description": "unauthorized" } } } }, "/api/comments": { "get": { "summary": "Comments", "security": [ ], "tags": [ "comments" ], "description": "This endpoint allows the client to retrieve all comments belonging to an article or podcast episode as threaded conversations.\n\n### Threaded Structure & Pagination Tips:\n- **Threaded Format**: Comments are returned as a tree structure (nested arrays of replies). Each top-level comment contains its nested child comments recursively.\n- **Query Constraints**: You must provide either `a_id` (Article ID) OR `p_id` (Podcast Episode ID) to fetch comments. Specifying both is not supported.\n- **Pagination**: When paginating, the `page` parameter filters the *top-level* comments only. All replies to those top-level comments are returned nested inline, regardless of page index.\n- If the `page` parameter is omitted, the response returns the full comment tree in a single payload.", "operationId": "getCommentsByArticleId", "parameters": [ { "$ref": "#/components/parameters/pageParam" }, { "$ref": "#/components/parameters/perPageParam30to1000" }, { "name": "a_id", "in": "query", "required": false, "description": "Article identifier. Provide this to fetch comments belonging to a specific article.", "schema": { "type": "string" }, "example": "321" }, { "name": "p_id", "in": "query", "required": false, "description": "Podcast Episode identifier. Provide this to fetch comments belonging to a specific podcast episode.", "schema": { "type": "string" }, "example": "321" }, { "name": "page", "in": "query", "required": false, "description": "Pagination page index for top-level comments.", "schema": { "type": "string" }, "example": "321" } ], "responses": { "200": { "description": "A List of Comments", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/Comment" } } } } }, "404": { "description": "Resource Not Found" } } } }, "/api/comments/{id}": { "get": { "summary": "Comment by id", "security": [ ], "tags": [ "comments" ], "description": "This endpoint allows the client to retrieve a specific comment and all of its nested descendant replies.\n\n### Integration Tip:\n- Handy for linking directly to a deep comment thread or loading individual comment replies on demand.", "operationId": "getCommentById", "parameters": [ { "name": "id", "in": "path", "required": true, "description": "Comment identifier (the unique alpha-numeric `id_code` of the comment).", "schema": { "type": "string" }, "example": "321" } ], "responses": { "200": { "description": "A List of the Comments" }, "404": { "description": "Comment Not Found" } } } }, "/api/concepts": { "get": { "summary": "Retrieve all accessible concepts", "tags": [ "concepts" ], "description": "Retrieve all accessible concepts in the system.\n\n### Concepts Overview:\n- Concepts are semantic tags generated automatically by analyzing article text using ML embeddings (`gemini-embedding-2`), rather than explicit user tags.\n- Primarily used for advanced semantic categorization, automated feeds, and interest mapping.", "parameters": [ { "name": "page", "in": "query", "required": false, "description": "Pagination page index.", "schema": { "type": "integer" } }, { "name": "per_page", "in": "query", "required": false, "description": "Number of items to return per page.", "schema": { "type": "integer" } }, { "name": "days", "in": "query", "required": false, "description": "Number of days of activity to aggregate for computing the concept popularity/trend score (default is 7 days).", "schema": { "type": "integer" } } ], "responses": { "200": { "description": "successful", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/Concept" } } } } }, "401": { "description": "unauthorized" } } } }, "/api/concepts/{id}": { "get": { "summary": "Retrieve details of a concept", "tags": [ "concepts" ], "description": "Retrieve details, settings, and popularity metrics of a single concept by ID.\n\n### Integration Tip:\n- Includes the semantic description, similarity thresholds, parent concept mappings, and scores.", "parameters": [ { "name": "id", "in": "path", "required": true, "description": "Unique concept numerical ID.", "schema": { "type": "integer" } }, { "name": "days", "in": "query", "required": false, "description": "Number of days of activity to aggregate for the concept popularity/trend score.", "schema": { "type": "integer" } } ], "responses": { "200": { "description": "successful", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Concept" } } } }, "401": { "description": "unauthorized" } } }, "patch": { "summary": "Update a concept's metadata", "tags": [ "concepts" ], "description": "Update concept metadata such as description, similarity threshold, and custom score.\n\n### Parameter Guidelines:\n- **similarity_threshold**: Cosine distance threshold (range 0.0 to 1.0) determining how closely an article's embedding must align with the concept's anchor embedding to be classified under it.", "parameters": [ { "name": "id", "in": "path", "required": true, "description": "Unique concept numerical ID.", "schema": { "type": "integer" } } ], "responses": { "200": { "description": "successful", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Concept" } } } } }, "requestBody": { "content": { "application/json": { "schema": { "type": "object", "properties": { "concept": { "type": "object", "properties": { "score": { "type": "number" }, "description": { "type": "string" }, "similarity_threshold": { "type": "number" } } } } } } } } } }, "/api/concepts/{id}/articles": { "get": { "summary": "Retrieve articles mapped to a concept", "tags": [ "concepts" ], "description": "Retrieve articles classified under this concept.\n\n### Parameter Guidelines:\n- **sort**: Set to `score` to sort articles by article popularity score descending. If omitted or set to any other value, sorting defaults to cosine similarity (closest first) secondary sorted by article score.", "parameters": [ { "name": "id", "in": "path", "required": true, "description": "Unique concept numerical ID.", "schema": { "type": "integer" } }, { "name": "sort", "in": "query", "required": false, "description": "Sorting criteria: `score` or default.", "schema": { "type": "string" } }, { "name": "page", "in": "query", "required": false, "description": "Pagination page index.", "schema": { "type": "integer" } }, { "name": "per_page", "in": "query", "required": false, "description": "Number of items to return per page.", "schema": { "type": "integer" } } ], "responses": { "200": { "description": "successful", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/ArticleIndex" } } } } } } } }, "/api/admin/concepts": { "get": { "summary": "Retrieve all concepts (Admin)", "tags": [ "concepts", "admin" ], "description": "Retrieve all concepts in the system including system and draft concepts. Admin credentials required.", "parameters": [ { "name": "page", "in": "query", "required": false, "schema": { "type": "integer" } }, { "name": "per_page", "in": "query", "required": false, "schema": { "type": "integer" } } ], "responses": { "200": { "description": "successful", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/Concept" } } } } } } }, "post": { "summary": "Create a concept (Admin)", "tags": [ "concepts", "admin" ], "description": "Create a new Concept.\n\n### Parameters:\n- **name**: Human readable label for the concept.\n- **description**: Detailed semantic definition used to generate the anchor embedding.\n- **similarity_threshold**: Target similarity threshold for categorizing articles under this concept.\n- **parent_id**: ID of parent concept, if establishing a hierarchy.", "parameters": [ ], "responses": { "201": { "description": "created", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Concept" } } } } }, "requestBody": { "content": { "application/json": { "schema": { "type": "object", "properties": { "concept": { "type": "object", "properties": { "name": { "type": "string" }, "description": { "type": "string" }, "parent_id": { "type": "integer", "nullable": true }, "similarity_threshold": { "type": "number" }, "score": { "type": "number" } }, "required": [ "name" ] } } } } } } } }, "/api/admin/concepts/{id}": { "get": { "summary": "Retrieve concept detail (Admin)", "tags": [ "concepts", "admin" ], "description": "Retrieve full details of a specific concept by ID. Admin credentials required.", "parameters": [ { "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } } ], "responses": { "200": { "description": "successful", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Concept" } } } } } }, "patch": { "summary": "Update a concept (Admin)", "tags": [ "concepts", "admin" ], "description": "Update concept properties. Admin credentials required.", "parameters": [ { "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } } ], "responses": { "200": { "description": "successful", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Concept" } } } } }, "requestBody": { "content": { "application/json": { "schema": { "type": "object", "properties": { "concept": { "type": "object", "properties": { "name": { "type": "string" }, "description": { "type": "string" }, "parent_id": { "type": "integer", "nullable": true }, "similarity_threshold": { "type": "number" }, "score": { "type": "number" } } } } } } } } }, "delete": { "summary": "Delete a concept (Admin)", "tags": [ "concepts", "admin" ], "description": "Permanently delete a concept by ID. Admin credentials required.", "parameters": [ { "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } } ], "responses": { "204": { "description": "no content" } } } }, "/api/admin/concepts/{id}/trigger_lookback": { "post": { "summary": "Trigger concept lookback backfill (Admin)", "tags": [ "concepts", "admin" ], "description": "Trigger a background backfill worker to scan historical articles published in the last `N` days and evaluate them against this concept.", "parameters": [ { "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } } ], "responses": { "200": { "description": "successful" } }, "requestBody": { "content": { "application/json": { "schema": { "type": "object", "properties": { "days": { "type": "integer" } }, "required": [ "days" ] } } } } } }, "/api/concepts/search": { "get": { "summary": "Perform a semantic fuzzy search on concepts", "tags": [ "concepts" ], "description": "Allows authenticated clients to search concepts using Forem's semantic embeddings database.", "parameters": [ { "name": "q", "in": "query", "required": true, "description": "The search query term to match semantically.", "schema": { "type": "string" } }, { "name": "per_page", "in": "query", "required": false, "description": "Limit of concepts returned (default 10, max 50).", "schema": { "type": "integer" } }, { "name": "threshold", "in": "query", "required": false, "description": "Optional cosine distance threshold (between 0.0 and 2.0) to filter results.", "schema": { "type": "number" } } ], "responses": { "200": { "description": "successful", "content": { "application/json": { "schema": { "type": "array", "items": { "type": "object", "properties": { "id": { "type": "integer" }, "name": { "type": "string" }, "slug": { "type": "string" }, "description": { "type": "string", "nullable": true }, "parent_id": { "type": "integer", "nullable": true }, "score": { "type": "number" }, "similarity_threshold": { "type": "number", "nullable": true }, "created_at": { "type": "string" }, "updated_at": { "type": "string" }, "distance": { "type": "number" }, "similarity": { "type": "number" } } } } } } }, "400": { "description": "bad request" }, "401": { "description": "unauthorized" } } } }, "/api/feedback_messages/{id}": { "patch": { "summary": "Update a feedback message's status (Admin)", "tags": [ "feedback_messages", "admin" ], "description": "Update the status of a user feedback message or report.\n\n### Feedback Messages Overview:\n- Feedback messages are submitted by users via support forms or content abuse reporting modals.\n- Requires Administrator privileges.\n- Used to track moderation/resolution workflows (e.g. marking a spam report as `Resolved`, `Spam`, or `Ignored`).", "parameters": [ { "name": "id", "in": "path", "required": true, "description": "Unique feedback message ID.", "schema": { "type": "integer" } } ], "responses": { "200": { "description": "successful" } }, "requestBody": { "content": { "application/json": { "schema": { "type": "object", "properties": { "feedback_message": { "type": "object", "properties": { "status": { "type": "string" } }, "required": [ "status" ] } } } } }, "description": "Feedback parameters containing the status updates." } } }, "/api/follows/tags": { "get": { "summary": "Followed Tags", "tags": [ "followed_tags", "tags" ], "description": "Retrieve the list of tags followed by the authenticated user.\n\n ### Integration & Personalization Guidance:\n - Requires authentication.\n - Returns tags in ascending/popularity order based on user interactions.\n - Useful for customizing the home feed interface, constructing personalized sidebar navigation, or displaying a user's customized topic preferences in a dashboard.", "operationId": "getFollowedTags", "responses": { "401": { "description": "unauthorized" }, "200": { "description": "A List of followed tags", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/FollowedTag" } } } } } } } }, "/api/followers/users": { "get": { "summary": "Followers", "tags": [ "followers" ], "description": "This endpoint allows the client to retrieve a list of the followers they have.\n\n ### Integration & Pagination Guidance:\n - \"Followers\" are other users registered on the platform who follow the authenticated user.\n - Supports pagination, defaulting to 80 followers per page.\n - The `sort` query parameter determines the sorting order based on when the follow relationship was established.", "operationId": "getFollowers", "parameters": [ { "$ref": "#/components/parameters/pageParam" }, { "$ref": "#/components/parameters/perPageParam30to1000" }, { "name": "sort", "in": "query", "required": false, "description": "Specifies the sort order for the follow relationship created_at field. Use `created_at` for chronological (oldest first) or `-created_at` for reverse chronological (newest first).", "schema": { "type": "string" }, "example": "created_at" } ], "responses": { "200": { "description": "A List of followers", "content": { "application/json": { "schema": { "type": "array", "items": { "description": "A follower", "type": "object", "properties": { "type_of": { "description": "user_follower by default", "type": "string" }, "id": { "type": "integer", "format": "int32" }, "user_id": { "description": "The follower's user id", "type": "integer", "format": "int32" }, "name": { "description": "The follower's name", "type": "string" }, "path": { "description": "A path to the follower's profile", "type": "string" }, "profile_image": { "description": "Profile image (640x640)", "type": "string" } } } } } } }, "401": { "description": "unauthorized" } } } }, "/api/follows": { "post": { "summary": "Follow users or organizations", "tags": [ "follows" ], "description": "Follow users or organizations in bulk on behalf of the authenticated user.\n\n### Bulk Update Behavior:\n- Accepts arrays of `user_ids` and `organization_ids` in the request body.\n- Performs follow actions for all provided identifiers.\n- Highly efficient for onboarding flows or importing social connections.", "parameters": [ ], "responses": { "200": { "description": "successful", "content": { "application/json": { "schema": { "type": "object", "properties": { "outcome": { "type": "string" } } } } } } }, "requestBody": { "content": { "application/json": { "schema": { "type": "object", "properties": { "user_ids": { "type": "array", "items": { "type": "integer" } }, "organization_ids": { "type": "array", "items": { "type": "integer" } } } } } }, "description": "Map containing lists of user and organization IDs to follow." } } }, "/api/health_checks/app": { "get": { "summary": "Check app health", "tags": [ "health_checks" ], "security": [ ], "description": "Basic application liveness check.\n\n### Usage Guidance:\n- Verifies that the Rails application process is running and responding to requests.\n- Does not check database or cache connectivity.\n- Typically used by load balancers, container orchestrators (like Kubernetes), or uptime checkers to verify container health.", "parameters": [ { "name": "health-check-token", "in": "header", "required": false, "description": "Access token required if token-based security is enabled in settings for health checks.", "schema": { "type": "string" } } ], "responses": { "200": { "description": "successful" } } } }, "/api/health_checks/database": { "get": { "summary": "Check database connection", "tags": [ "health_checks" ], "security": [ ], "description": "Database connection health check.\n\n### Usage Guidance:\n- Verifies that the application can successfully query the primary PostgreSQL database.\n- Used to monitor database pool status and connection health.", "parameters": [ { "name": "health-check-token", "in": "header", "required": false, "description": "Access token required if token-based security is enabled in settings for health checks.", "schema": { "type": "string" } } ], "responses": { "200": { "description": "successful" } } } }, "/api/health_checks/cache": { "get": { "summary": "Check cache connection", "tags": [ "health_checks" ], "security": [ ], "description": "Cache connection health check.\n\n### Usage Guidance:\n- Verifies that the application can successfully ping the Redis cache instance.\n- Used to monitor cache and background worker queue connection health.", "parameters": [ { "name": "health-check-token", "in": "header", "required": false, "description": "Access token required if token-based security is enabled in settings for health checks.", "schema": { "type": "string" } } ], "responses": { "200": { "description": "successful" } } } }, "/api/instance": { "get": { "summary": "Retrieve instance configuration details", "tags": [ "instance" ], "description": "Retrieve configuration details for the current Forem instance.\n\n### Instance Metadata:\n- Bypasses authentication.\n- Returns public Forem version, branding parameters, community guidelines references, and supported features configurations.", "security": [ ], "responses": { "200": { "description": "successful" } } } }, "/api/organizations/{username}": { "get": { "summary": "An organization (by username)", "tags": [ "organizations" ], "security": [ ], "description": "Retrieve public profile information for a single organization by its username.\n\n### Integration Tip:\n- Bypasses authentication.\n- Returns details like Tech Stack, Tagline, Story, website URLs, and joined date.", "operationId": "getOrganization", "parameters": [ { "name": "username", "in": "path", "required": true, "description": "The unique slugified username of the organization (e.g. `github`).", "schema": { "type": "string" } } ], "responses": { "200": { "description": "An Organization", "content": { "application/json": { "schema": { "type": "object", "items": { "$ref": "#/components/schemas/Organization" } } } } }, "404": { "description": "Not Found" } } } }, "/api/organizations/{organization_id_or_username}/users": { "get": { "summary": "Organization's users", "tags": [ "organizations", "users" ], "security": [ ], "description": "Retrieve a list of public user profiles associated with the organization.\n\n### Path Parameter Options:\n- **organization_id_or_username**: Supports either the organization's unique numerical ID OR its string username (slug).\n- Ideal for displaying team member directory lists on organization/brand pages.", "operationId": "getOrgUsers", "parameters": [ { "name": "organization_id_or_username", "in": "path", "required": true, "description": "The organization's numerical ID or string username.", "schema": { "type": "string" } }, { "$ref": "#/components/parameters/pageParam" }, { "$ref": "#/components/parameters/perPageParam30to1000" } ], "responses": { "200": { "description": "An Organization's users (with ID)", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/User" } } } } }, "404": { "description": "Not Found" } } } }, "/api/organizations/{organization_id_or_username}/articles": { "get": { "summary": "Organization's Articles", "tags": [ "organizations", "articles" ], "security": [ ], "description": "Retrieve a list of articles published by the organization.\n\n### Path Parameter Options:\n- **organization_id_or_username**: Supports either the organization's unique numerical ID OR its string username (slug).\n- Returns articles in reverse chronological publication order.\n- Ideal for populating an organization's custom blog feed or publication listing.", "operationId": "getOrgArticles", "parameters": [ { "name": "organization_id_or_username", "in": "path", "required": true, "description": "The organization's numerical ID or string username.", "schema": { "type": "string" } }, { "$ref": "#/components/parameters/pageParam" }, { "$ref": "#/components/parameters/perPageParam30to1000" } ], "responses": { "200": { "description": "An Organization's Articles (with ID)", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/ArticleIndex" } } } } }, "404": { "description": "Not Found" } } } }, "/api/organizations": { "get": { "summary": "Organizations", "tags": [ "organizations" ], "security": [ ], "description": "This endpoint allows the client to retrieve a list of Dev organizations.\n\n It supports pagination, each page will contain 10 tags by default.", "operationId": "getOrganizations", "parameters": [ { "$ref": "#/components/parameters/pageParam" }, { "$ref": "#/components/parameters/perPageParam10to1000" } ], "responses": { "200": { "description": "A list of all organizations", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/Organization" } } } } } } }, "post": { "summary": "Create an Organization", "tags": [ "organizations" ], "description": "This endpoint allows the client to create an organization with the provided parameters.\n It requires a token from a user with `admin` privileges.", "operationId": "createOrganization", "parameters": [ ], "responses": { "201": { "description": "Successful" }, "401": { "description": "Unauthorized" }, "422": { "description": "Unprocessable Entity" } }, "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Organization" } } }, "description": "Representation of Organization to be created" } } }, "/api/organizations/{id}": { "get": { "summary": "An organization (by id)", "tags": [ "organizations" ], "security": [ ], "description": "This endpoint allows the client to retrieve a single organization by their id", "operationId": "getOrganizationById", "parameters": [ { "name": "id", "in": "path", "required": true, "schema": { "type": "integer" } } ], "responses": { "200": { "description": "An Organization", "content": { "application/json": { "schema": { "type": "object", "items": { "$ref": "#/components/schemas/Organization" } } } } }, "404": { "description": "Not Found" } } }, "put": { "summary": "Update an organization by id", "tags": [ "organizations" ], "description": "This endpoint allows the client to update an existing organization.", "parameters": [ { "name": "id", "in": "path", "required": true, "description": "The ID of the organization to update.", "schema": { "type": "integer", "format": "int32", "minimum": 1 }, "example": 123 } ], "responses": { "200": { "description": "An Organization" }, "404": { "description": "organization Not Found" }, "401": { "description": "Unauthorized" }, "422": { "description": "Unprocessable Entity" } }, "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Organization" } } }, "description": "Representation of Organization to be updated" } }, "delete": { "summary": "Delete an Organization by id", "tags": [ "organizations" ], "description": "This endpoint allows the client to delete a single organization, specified by id", "parameters": [ { "name": "id", "in": "path", "required": true, "description": "The ID of the organization.", "schema": { "type": "integer", "format": "int32", "minimum": 1 }, "example": 1 } ], "responses": { "200": { "description": "successful" }, "401": { "description": "unauthorized" } } } }, "/api/pages": { "get": { "summary": "show details for all pages", "security": [ ], "tags": [ "pages" ], "description": "Retrieve details for all Page objects in the system.\n\n ### Pages Overview:\n - Pages are custom static or dynamic views hosted on the Forem instance.\n - Publicly visible unless restricted.\n - Helpful for building custom menus, rendering site policies, or embedding custom forms.", "responses": { "200": { "description": "successful", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/Page" } } } } } } }, "post": { "summary": "pages", "tags": [ "pages" ], "description": "Create a new custom page. Requires administrative privileges.\n\n ### Body Parameter Guidelines:\n - **title**: Heading displayed at the top of the page.\n - **slug**: URL path identifier. Must be unique and URL-safe.\n - **body_markdown**: Content written in Markdown format.\n - **body_json**: Optional JSON payload for API-consumed/structured data pages.\n - **is_top_level_path**: Set to `true` to serve the page directly at the root (e.g. `/{slug}`) instead of under the default namespace `/page/{slug}`. Use with caution to avoid namespace collisions with core Forem paths.\n - **template**: Layout styling options (`contained` or custom layouts).", "parameters": [ ], "responses": { "200": { "description": "successful" }, "401": { "description": "unauthorized" }, "422": { "description": "unprocessable" } }, "requestBody": { "content": { "application/json": { "schema": { "type": "object", "properties": { "title": { "type": "string", "description": "Title of the page" }, "slug": { "type": "string", "description": "Used to link to this page in URLs, must be unique and URL-safe" }, "description": { "type": "string", "description": "For internal use, helps similar pages from one another" }, "body_markdown": { "type": "string", "description": "The text (in markdown) of the page (required)" }, "body_json": { "type": "string", "description": "For JSON pages, the JSON body" }, "is_top_level_path": { "type": "boolean", "description": "If true, the page is available at '/{slug}' instead of '/page/{slug}', use with caution" }, "template": { "type": "string", "enum": [ "contained", "full_within_layout", "nav_bar_included", "json", "css", "txt" ], "default": "contained", "description": "Controls what kind of layout the page is rendered in" } } } } }, "description": "Page parameters." } } }, "/api/pages/{id}": { "get": { "summary": "show details for a page", "security": [ ], "tags": [ "pages" ], "description": "Retrieve details for a single Page object specified by ID.", "parameters": [ { "name": "id", "in": "path", "required": true, "description": "The unique ID of the page.", "schema": { "type": "integer", "format": "int32", "minimum": 1 }, "example": 1 } ], "responses": { "200": { "description": "successful", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Page" } } } } } }, "put": { "summary": "update details for a page", "tags": [ "pages" ], "description": "Update an existing page's details by ID. Requires administrative privileges.", "parameters": [ { "name": "id", "in": "path", "required": true, "description": "The ID of the page to update.", "schema": { "type": "integer", "format": "int32", "minimum": 1 }, "example": 1 } ], "responses": { "200": { "description": "successful", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Page" } } } }, "401": { "description": "unauthorized" }, "422": { "description": "unprocessable" } }, "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Page" } } }, "description": "Representation of Page to be updated" } }, "delete": { "summary": "remove a page", "tags": [ "pages" ], "description": "Delete a custom page from the system by ID. Requires administrative privileges.", "parameters": [ { "name": "id", "in": "path", "required": true, "description": "The ID of the page to delete.", "schema": { "type": "integer", "format": "int32", "minimum": 1 }, "example": 1 } ], "responses": { "200": { "description": "successful", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/Page" } } } }, "401": { "description": "unauthorized" }, "422": { "description": "unprocessable" } } } }, "/api/podcast_episodes": { "get": { "summary": "Podcast Episodes", "security": [ ], "tags": [ "podcast_episodes" ], "description": "Retrieve a list of podcast episodes published on the platform.\n\n ### Integration Guidance:\n - Bypasses authentication (can be accessed publicly).\n - Only returns active episodes belonging to published/reachable podcasts.\n - Episodes are returned in reverse chronological order based on their publication date.\n - The `username` query parameter is the unique slug of the podcast channel (e.g. `codenewbie`).\n\n It supports pagination, each page will contain 30 episodes by default.", "operationId": "getPodcastEpisodes", "parameters": [ { "$ref": "#/components/parameters/pageParam" }, { "$ref": "#/components/parameters/perPageParam30to1000" }, { "name": "username", "in": "query", "required": false, "description": "Filters episodes by the unique slug (username) of the podcast (e.g. 'codenewbie').", "schema": { "type": "string" }, "example": "codenewbie" } ], "responses": { "200": { "description": "A List of Podcast episodes filtered by username", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/PodcastEpisodeIndex" } } } } }, "404": { "description": "Unknown Podcast username" } } } }, "/api/profile_images/{username}": { "get": { "summary": "A Users or organizations profile image", "tags": [ "profile images" ], "description": "Retrieve the profile image URL and configuration for a user or organization by username.\n\n ### Integration Details:\n - Bypasses authentication.\n - Convenient for quickly displaying avatars/icons in comments lists, headers, or sidebar elements without requesting the full user profile model.", "operationId": "getProfileImage", "parameters": [ { "name": "username", "in": "path", "required": true, "description": "The unique username of the target user or organization.", "schema": { "type": "string" }, "example": "janedoe" } ], "responses": { "200": { "description": "An object containing profile image details", "content": { "application/json": { "schema": { "type": "object", "items": { "$ref": "#/components/schemas/ProfileImage" } } } } }, "404": { "description": "Resource Not Found" } } } }, "/api/reactions/toggle": { "post": { "summary": "toggle reaction", "tags": [ "reactions" ], "description": "Toggle a reaction on a target resource (Article, Comment, or User) on behalf of the authenticated user.\n\n ### Toggle Logic:\n - **First Request**: Creates a new reaction of the specified category on the reactable target.\n - **Second Request (with same parameters)**: Deletes the existing reaction.\n - Particularly useful for simple, interactive UI buttons like \"Like\", \"Unicorn\", or \"Save\" where clicking toggles the active state.", "parameters": [ { "name": "category", "in": "query", "required": true, "description": "The type of reaction (e.g. `like` for standard likes, `unicorn` for outstanding posts, `save` for bookmarking to the reading list).", "schema": { "type": "string", "enum": [ "like", "unicorn", "exploding_head", "raised_hands", "fire" ] } }, { "name": "reactable_id", "in": "query", "required": true, "description": "The unique numerical ID of the target resource (Article, Comment, or User) being reacted to.", "schema": { "type": "integer", "format": "int32" } }, { "name": "reactable_type", "in": "query", "required": true, "description": "The class name of the target resource being reacted to (e.g. `Article`, `Comment`, `User`).", "schema": { "type": "string", "enum": [ "Comment", "Article", "User" ] } } ], "responses": { "200": { "description": "successful" }, "401": { "description": "unauthorized" } } } }, "/api/reactions": { "post": { "summary": "create reaction", "tags": [ "reactions" ], "description": "Create a reaction on a target resource (Article, Comment, or User) on behalf of the authenticated user.\n\n ### Usage Details:\n - Unlike the toggle endpoint, this endpoint is idempotent: multiple requests to react with the same category to the same target will return the existing reaction without deleting it.", "parameters": [ { "name": "category", "in": "query", "required": true, "description": "The type of reaction (e.g. `like` for standard likes, `unicorn` for outstanding posts, `save` for bookmarking to the reading list).", "schema": { "type": "string", "enum": [ "like", "unicorn", "exploding_head", "raised_hands", "fire" ] } }, { "name": "reactable_id", "in": "query", "required": true, "description": "The unique numerical ID of the target resource (Article, Comment, or User) being reacted to.", "schema": { "type": "integer", "format": "int32" } }, { "name": "reactable_type", "in": "query", "required": true, "description": "The class name of the target resource being reacted to (e.g. `Article`, `Comment`, `User`).", "schema": { "type": "string", "enum": [ "Comment", "Article", "User" ] } } ], "responses": { "200": { "description": "successful" }, "401": { "description": "unauthorized" } } } }, "/api/readinglist": { "get": { "summary": "Readinglist", "tags": [ "readinglist" ], "description": "Retrieve the list of articles saved to the authenticated user's reading list.\n\n ### Integration Guidance:\n - Requires authentication.\n - Under the hood, this endpoint retrieves articles that the user has reacted to with the `\"save\"` reaction category.\n - Supports pagination, defaulting to 30 articles per page.\n - Returned objects conform to the standard `ArticleIndex` schema.", "operationId": "getReadinglist", "parameters": [ { "$ref": "#/components/parameters/pageParam" }, { "$ref": "#/components/parameters/perPageParam30to1000" } ], "responses": { "401": { "description": "Unauthorized" }, "200": { "description": "A list of articles in the users readinglist", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/ArticleIndex" } } } } } } } }, "/api/recommended_articles_lists": { "get": { "summary": "Retrieve all recommended articles lists", "tags": [ "recommended_articles_lists" ], "description": "Retrieve a list of all recommended articles lists configured in the system.\n\n### Overview:\n- Recommended Articles Lists are curated selections of articles pinned or recommended in layout regions (e.g. `main_feed` or `sidebar`).\n- Supports search by list name and standard query pagination.\n- Requires Administrator privileges.", "parameters": [ { "name": "page", "in": "query", "required": false, "description": "Pagination page index.", "schema": { "type": "integer" } }, { "name": "search", "in": "query", "required": false, "description": "Search term to match against recommended list names.", "schema": { "type": "string" } } ], "responses": { "200": { "description": "successful", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/RecommendedArticlesList" } } } } } } }, "post": { "summary": "Create or update a recommended articles list", "tags": [ "recommended_articles_lists" ], "description": "Create a new recommended articles list or update an existing one. Requires Administrator privileges.\n\n### Body Parameter Guidelines:\n- **name**: Curated list title or label (e.g. \"Editor's Choice\").\n- **placement_area**: Target UI layout region (e.g. `main_feed`, `sidebar_top`, `onboarding`).\n- **expires_at**: ISO 8601 timestamp after which the list recommendation automatically expires.\n- **user_id**: Owner/curator numeric user ID.\n- **article_ids**: Staged array of numeric article IDs to include in the recommendation sequence.", "parameters": [ ], "responses": { "201": { "description": "created", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/RecommendedArticlesList" } } } } }, "requestBody": { "content": { "application/json": { "schema": { "type": "object", "properties": { "name": { "type": "string" }, "placement_area": { "type": "string" }, "expires_at": { "type": "string", "format": "date-time" }, "user_id": { "type": "integer" }, "article_ids": { "type": "array", "items": { "type": "integer" } } }, "required": [ "user_id", "placement_area" ] } } }, "description": "Curated recommended list details." } } }, "/api/recommended_articles_lists/{id}": { "get": { "summary": "Retrieve details of a recommended articles list", "tags": [ "recommended_articles_lists" ], "description": "Retrieve full details and nested article lists for a specific recommended articles list by ID. Requires Admin credentials.", "parameters": [ { "name": "id", "in": "path", "required": true, "description": "Unique list numeric ID.", "schema": { "type": "integer" } } ], "responses": { "200": { "description": "successful", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/RecommendedArticlesList" } } } } } }, "patch": { "summary": "Update a recommended articles list", "tags": [ "recommended_articles_lists" ], "description": "Update an existing recommended articles list by ID. Requires Admin credentials.", "parameters": [ { "name": "id", "in": "path", "required": true, "description": "Unique list numeric ID.", "schema": { "type": "integer" } } ], "responses": { "200": { "description": "successful", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/RecommendedArticlesList" } } } } }, "requestBody": { "content": { "application/json": { "schema": { "type": "object", "properties": { "name": { "type": "string" }, "placement_area": { "type": "string" }, "expires_at": { "type": "string", "format": "date-time" }, "user_id": { "type": "integer" }, "article_ids": { "type": "array", "items": { "type": "integer" } } } } } }, "description": "Updated list parameters." } } }, "/api/admin/request_redirects": { "get": { "summary": "Retrieve all request redirects (Admin)", "tags": [ "request_redirects", "admin" ], "description": "Retrieve a list of all request redirects configured on the platform.\n\n### Redirects Overview:\n- Redirects map incoming HTTP request paths or domains to specific target destination URLs (301 or 302 redirects).\n- Primarily used for managing vanity URLs, legacy path support, or domain migrations.\n- Requires Administrator privileges.", "parameters": [ { "name": "page", "in": "query", "required": false, "description": "Pagination page index.", "schema": { "type": "integer" } }, { "name": "per_page", "in": "query", "required": false, "description": "Number of items to return per page.", "schema": { "type": "integer" } } ], "responses": { "200": { "description": "successful", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/RequestRedirect" } } } } } } }, "post": { "summary": "Create a request redirect (Admin)", "tags": [ "request_redirects", "admin" ], "description": "Create a new request redirect route. Requires Administrator privileges.\n\n### Body Parameter Guidelines:\n- **original_url**: The source path (e.g. `/old-page`) to intercept.\n- **destination_url**: The target destination (e.g. `https://myforem.com/new-page`) to redirect users to.\n- **request_domain**: The domain scope (e.g. `dev.to`) where this redirect rule applies.", "parameters": [ ], "responses": { "201": { "description": "created", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/RequestRedirect" } } } } }, "requestBody": { "content": { "application/json": { "schema": { "type": "object", "properties": { "request_redirect": { "type": "object", "properties": { "original_url": { "type": "string" }, "destination_url": { "type": "string" }, "request_domain": { "type": "string" } }, "required": [ "original_url", "destination_url", "request_domain" ] } } } } }, "description": "Redirect routing parameters." } } }, "/api/admin/request_redirects/{id}": { "get": { "summary": "Retrieve a request redirect's details (Admin)", "tags": [ "request_redirects", "admin" ], "description": "Retrieve details of a single request redirect route by ID. Requires Admin credentials.", "parameters": [ { "name": "id", "in": "path", "required": true, "description": "Unique redirect ID.", "schema": { "type": "integer" } } ], "responses": { "200": { "description": "successful", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/RequestRedirect" } } } } } }, "patch": { "summary": "Update a request redirect (Admin)", "tags": [ "request_redirects", "admin" ], "description": "Update details (paths or domain scope) of an existing redirect route by ID. Requires Admin credentials.", "parameters": [ { "name": "id", "in": "path", "required": true, "description": "Unique redirect ID to update.", "schema": { "type": "integer" } } ], "responses": { "200": { "description": "successful", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/RequestRedirect" } } } } }, "requestBody": { "content": { "application/json": { "schema": { "type": "object", "properties": { "request_redirect": { "type": "object", "properties": { "original_url": { "type": "string" }, "destination_url": { "type": "string" }, "request_domain": { "type": "string" } } } } } } }, "description": "Updated redirect routing parameters." } }, "delete": { "summary": "Delete a request redirect (Admin)", "tags": [ "request_redirects", "admin" ], "description": "Delete a request redirect route, disabling the routing intercept immediately. Requires Admin credentials.", "parameters": [ { "name": "id", "in": "path", "required": true, "description": "Unique redirect ID to delete.", "schema": { "type": "integer" } } ], "responses": { "204": { "description": "no content" } } } }, "/api/subforems": { "get": { "summary": "Retrieve all discoverable subforems", "tags": [ "subforems" ], "description": "Retrieve a list of all discoverable subforems/communities.\n\n### Subforems Overview:\n- Subforems represent distinct sub-communities or specialized sections hosted within the Forem instance.\n- Bypasses authentication (can be accessed publicly).\n- Returns list of names, slugs, color schemes, and target interests.", "security": [ ], "responses": { "200": { "description": "successful", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/Subforem" } } } } } } } }, "/api/surveys": { "get": { "summary": "List surveys", "tags": [ "surveys" ], "description": "Retrieve a list of surveys configured on the platform.\n\n### Surveys Overview:\n- Surveys are admin-defined questionnaires consisting of multiple choice or text polls.\n- Requires Administrator authorization.\n- Supports standard pagination controls and active status filtering.", "operationId": "getSurveys", "parameters": [ { "$ref": "#/components/parameters/pageParam" }, { "$ref": "#/components/parameters/perPageParam30to1000" }, { "name": "active", "in": "query", "required": false, "description": "Filter by active status. Omit to return all surveys.", "schema": { "type": "boolean" } } ], "responses": { "200": { "description": "A list of surveys", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/Survey" } } } } }, "401": { "description": "Unauthorized" } } }, "post": { "summary": "Create a survey", "tags": [ "surveys" ], "description": "Create a new survey with optional nested polls and poll options. Requires Administrator privileges.", "parameters": [ ], "responses": { "201": { "description": "Created", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SurveyWithPolls" } } } }, "422": { "description": "Unprocessable Entity" }, "401": { "description": "Unauthorized" } }, "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SurveyInput" } } }, "description": "Survey properties to create." } } }, "/api/surveys/{id_or_slug}": { "get": { "summary": "A survey with polls", "tags": [ "surveys" ], "description": "Retrieve a single survey (by ID or slug) with its nested structure.\n\n### Nested Format Details:\n- Returns the target Survey object including all associated Polls, multiple choice options, and configuration states.\n- Requires Administrator authorization.", "operationId": "getSurveyByIdOrSlug", "parameters": [ { "name": "id_or_slug", "in": "path", "required": true, "description": "The ID or slug of the survey.", "schema": { "type": "string" }, "example": "community-pulse-2026" } ], "responses": { "200": { "description": "A survey with nested polls and options", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SurveyWithPolls" } } } }, "401": { "description": "Unauthorized" }, "404": { "description": "Not found" } } }, "patch": { "summary": "Update a survey", "tags": [ "surveys" ], "description": "Update an existing survey, including its polls and options. Requires Administrator privileges.", "parameters": [ { "name": "id_or_slug", "in": "path", "required": true, "description": "The ID or slug of the survey.", "schema": { "type": "string" } } ], "responses": { "200": { "description": "Successful", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SurveyWithPolls" } } } }, "404": { "description": "Not Found" }, "401": { "description": "Unauthorized" }, "422": { "description": "Unprocessable Entity" } }, "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/SurveyInput" } } }, "description": "Survey properties to update." } }, "delete": { "summary": "Delete a survey", "tags": [ "surveys" ], "description": "Delete an existing survey. Requires Administrator privileges.", "parameters": [ { "name": "id_or_slug", "in": "path", "required": true, "description": "The ID or slug of the survey.", "schema": { "type": "string" } } ], "responses": { "204": { "description": "No Content" }, "404": { "description": "Not Found" }, "401": { "description": "Unauthorized" }, "422": { "description": "Unprocessable Entity" } } } }, "/api/surveys/{id_or_slug}/poll_votes": { "get": { "summary": "Survey poll votes", "tags": [ "surveys" ], "description": "Retrieve multiple-choice poll votes for a specific survey.\n\n### Cursor Pagination Tip:\n- Uses cursor-based pagination to safely stream high volumes of voting records.\n- Specify the `after` query parameter with the last retrieved record ID to get the next page.\n- Requires Administrator authorization.", "operationId": "getSurveyPollVotes", "parameters": [ { "name": "id_or_slug", "in": "path", "required": true, "description": "The ID or slug of the survey.", "schema": { "type": "string" } }, { "$ref": "#/components/parameters/perPageParam30to1000" }, { "name": "after", "in": "query", "required": false, "description": "Return only votes with an ID greater than this value.", "schema": { "type": "integer" }, "example": 42 } ], "responses": { "200": { "description": "Poll votes", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/PollVote" } } } } }, "401": { "description": "Unauthorized" }, "404": { "description": "Not found" } } } }, "/api/surveys/{id_or_slug}/poll_text_responses": { "get": { "summary": "Survey poll text responses", "tags": [ "surveys" ], "description": "Retrieve free-text poll responses for a specific survey.\n\n### Integration & Cursor Tip:\n- Fetches written user answers for text-input questions.\n- Uses cursor-based pagination (`after` query param) to stream responses.\n- Requires Administrator authorization.", "operationId": "getSurveyPollTextResponses", "parameters": [ { "name": "id_or_slug", "in": "path", "required": true, "description": "The ID or slug of the survey.", "schema": { "type": "string" } }, { "$ref": "#/components/parameters/perPageParam30to1000" }, { "name": "after", "in": "query", "required": false, "description": "Return only text responses with an ID greater than this value.", "schema": { "type": "integer" }, "example": 42 } ], "responses": { "200": { "description": "Poll text responses", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/PollTextResponse" } } } } }, "401": { "description": "Unauthorized" }, "404": { "description": "Not found" } } } }, "/api/tags": { "get": { "summary": "Tags", "tags": [ "tags" ], "security": [ ], "description": "Retrieve a list of tags used on the platform, ordered by popularity.\n\n### Integration Tips:\n- Bypasses authentication (can be accessed publicly).\n- Ordered by overall popularity/usage metrics on the platform.\n- Useful for autocomplete inputs in article editors, tag selection screens in settings, or rendering global navigation/explore lists.\n\nIt supports pagination, each page will contain 10 tags by default.", "operationId": "getTags", "parameters": [ { "$ref": "#/components/parameters/pageParam" }, { "$ref": "#/components/parameters/perPageParam10to1000" } ], "responses": { "200": { "description": "A List of all tags", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/Tag" } } } } } } } }, "/api/trends": { "get": { "summary": "Trends", "security": [ ], "tags": [ "trends" ], "description": "Retrieve a list of active trends.\n\n### Trends Overview & Score Calculation:\n- Trends represent hot topics or semantic themes currently being heavily discussed in the community.\n- They are computed by clustering semantic concept embeddings of recently published articles.\n- The `score` reflects the volume and engagement (views, comments, reactions) of articles associated with the trend.\n- Returned trends are ordered by score and recency.\n- Publicly accessible without authentication.\n\nIt supports pagination, each page will contain 10 trends by default.", "operationId": "getTrends", "parameters": [ { "$ref": "#/components/parameters/pageParam" }, { "$ref": "#/components/parameters/perPageParam10to1000" } ], "responses": { "200": { "description": "A List of Trends", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/Trend" } } } } } } } }, "/api/trends/{id_or_slug}": { "get": { "summary": "A Trend", "security": [ ], "tags": [ "trends" ], "description": "Retrieve details of a single trend using either its numeric ID or unique slug.\n\n### Usage Guidance:\n- Useful for loading details of a trending topic (description, score, and slug details) to render header sections on trending tag or topic pages.", "operationId": "getTrend", "parameters": [ { "name": "id_or_slug", "in": "path", "required": true, "description": "The ID or slug of the trend to retrieve.", "example": "ruby-3-4-release", "schema": { "type": "string" } } ], "responses": { "200": { "description": "A Trend", "content": { "application/json": { "schema": { "type": "object", "items": { "$ref": "#/components/schemas/Trend" } } } } }, "404": { "description": "Trend Not Found" } } } }, "/api/trends/{trend_id_or_slug}/articles": { "get": { "summary": "Articles in a Trend", "security": [ ], "tags": [ "trends" ], "description": "Retrieve a list of published articles belonging to a trend.\n\n### Article Ordering & Proximity:\n- Articles are mapped to trends based on their embedding distance to the trend's centroid.\n- Returned articles are ordered by proximity/similarity (distance) first (most relevant posts first), and then by overall article engagement score.\n- Supports pagination, each page will contain 10 articles by default.", "operationId": "getTrendArticles", "parameters": [ { "name": "trend_id_or_slug", "in": "path", "required": true, "description": "The ID or slug of the trend to retrieve articles for.", "example": "ruby-3-4-release", "schema": { "type": "string" } }, { "$ref": "#/components/parameters/pageParam" }, { "$ref": "#/components/parameters/perPageParam10to1000" } ], "responses": { "200": { "description": "A List of Articles in the Trend", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/ArticleIndex" } } } } }, "404": { "description": "Trend Not Found" } } } }, "/api/users/{id}/suspend": { "put": { "summary": "Suspend a User", "tags": [ "users" ], "description": "Suspend a user's account.\n\n### Suspension Details:\n- The user associated with the API key must have an `admin` or `moderator` role.\n- Assigns the `suspended` role to the user, preventing them from posting new content (articles or comments) while suspended.\n- Existing content is not deleted.\n- Suspended users are not notified in the UI; if you want them to know, you must notify them explicitly.", "operationId": "suspendUser", "parameters": [ { "name": "id", "in": "path", "required": true, "description": "The unique ID of the user to suspend.", "schema": { "type": "integer", "format": "int32", "minimum": 1 }, "example": 1 } ], "responses": { "204": { "description": "User successfully unpublished" }, "401": { "description": "Unauthorized" }, "404": { "description": "Unknown User ID" } } } }, "/api/users/{id}/limited": { "put": { "summary": "Add limited role for a User", "tags": [ "users" ], "description": "Limit a user's account permissions.\n\n### Limited Details:\n- Requires `admin` or `moderator` privileges.\n- Assigns the `limited` role, which rate-limits notifications generated by their actions (such as publishing new posts or comments).\n- Does not restrict content creation or delete content.", "operationId": "limitUser", "parameters": [ { "name": "id", "in": "path", "required": true, "description": "The ID of the user to limit.", "schema": { "type": "integer", "format": "int32", "minimum": 1 }, "example": 1 } ], "responses": { "204": { "description": "User successfully limited" }, "401": { "description": "Unauthorized" }, "404": { "description": "Unknown User ID" } } }, "delete": { "summary": "Remove limited for a User", "tags": [ "users" ], "description": "Remove the limited role, restoring the user to general privileges. Requires `admin` or `moderator` role.", "operationId": "unLimitUser", "parameters": [ { "name": "id", "in": "path", "required": true, "description": "The ID of the user to un-limit.", "schema": { "type": "integer", "format": "int32", "minimum": 1 }, "example": 1 } ], "responses": { "204": { "description": "User successfully un-limited" }, "401": { "description": "Unauthorized" }, "404": { "description": "Unknown User ID" } } } }, "/api/users/{id}/spam": { "put": { "summary": "Add spam role for a User", "tags": [ "users" ], "description": "Flag a user as spammer.\n\n### Spam Role Details:\n- Requires `admin` or `moderator` privileges.\n- Stops the user from creating new posts or comments, acting as a shadowban or block on activity.\n- Existing content remains but is hidden from search/feeds.", "operationId": "spamUser", "parameters": [ { "name": "id", "in": "path", "required": true, "description": "The ID of the user to assign the spam role.", "schema": { "type": "integer", "format": "int32", "minimum": 1 }, "example": 1 } ], "responses": { "204": { "description": "Spam role assigned to the user successfully" }, "401": { "description": "Unauthorized" }, "404": { "description": "Unknown User ID" } } }, "delete": { "summary": "Remove spam role from a User", "tags": [ "users" ], "description": "Unflag a user as spammer, restoring general permissions. Requires `admin` or `moderator` role.", "operationId": "unSpamUser", "parameters": [ { "name": "id", "in": "path", "required": true, "description": "The ID of the user to remove the spam role from.", "schema": { "type": "integer", "format": "int32", "minimum": 1 }, "example": 1 } ], "responses": { "204": { "description": "Successfully removed the spam role from a user" }, "401": { "description": "Unauthorized" }, "404": { "description": "Unknown User ID" } } } }, "/api/users/{id}/trusted": { "put": { "summary": "Add trusted role for a User", "tags": [ "users" ], "description": "Assign the trusted role to a user.\n\n### Trusted Role Details:\n- Requires `admin` or `moderator` privileges.\n- Trusted users gain privileges like casting flag votes on inappropriate content or participating in priority review queues.\n- Triggers a system notification and an email to the user.", "operationId": "trustUser", "parameters": [ { "name": "id", "in": "path", "required": true, "description": "The ID of the user to assign the trusted role.", "schema": { "type": "integer", "format": "int32", "minimum": 1 }, "example": 1 } ], "responses": { "204": { "description": "Trusted role assigned to the user successfully" }, "401": { "description": "Unauthorized" }, "404": { "description": "Unknown User ID" } } }, "delete": { "summary": "Remove trusted role from a User", "tags": [ "users" ], "description": "Revoke the trusted role, returning the user to general status. Requires `admin` or `moderator` role.", "operationId": "unTrustUser", "parameters": [ { "name": "id", "in": "path", "required": true, "description": "The ID of the user to remove the trusted role from.", "schema": { "type": "integer", "format": "int32", "minimum": 1 }, "example": 1 } ], "responses": { "204": { "description": "Successfully removed the trusted role from a user" }, "401": { "description": "Unauthorized" }, "404": { "description": "Unknown User ID" } } } }, "/api/users/me": { "get": { "summary": "The authenticated user", "tags": [ "users" ], "description": "This endpoint allows the client to retrieve information about the authenticated user.\n\n### Usage Tips:\n- Requires a valid `api-key` header to identify the user.\n- Useful for checking permissions, verifying linking state, or retrieving user-specific profile settings.", "operationId": "getUserMe", "responses": { "200": { "description": "successful", "content": { "application/json": { "schema": { "type": "object", "items": { "$ref": "#/components/schemas/MyUser" } } } } }, "401": { "description": "Unauthorized" } } } }, "/api/users/{id}": { "get": { "summary": "A User", "tags": [ "users" ], "security": [ ], "description": "This endpoint allows the client to retrieve a single user, either by id or by the user's username.\n\n### Path Parameter Options:\n- **id**: Can be either the user's unique numerical ID (e.g. `123`) OR the user's string username (e.g. `ben`).\n- Note that the returned user object schema (`ExtendedUser`) includes extended profile statistics and social link details.", "operationId": "getUser", "parameters": [ { "name": "id", "in": "path", "required": true, "description": "The user's unique numerical ID or string username.", "schema": { "type": "string" } } ], "responses": { "200": { "description": "successful", "content": { "application/json": { "schema": { "type": "object", "items": { "$ref": "#/components/schemas/ExtendedUser" } } } } } } } }, "/api/users/search": { "get": { "summary": "Search for users", "tags": [ "users" ], "description": "Search for a user by email address.\n\n### Permissions & Context:\n- Requires administrative privileges (`api-key` of an administrator).\n- Used to verify account existence or map email addresses to platform usernames.", "operationId": "searchUsers", "parameters": [ { "name": "email", "in": "query", "required": true, "description": "The exact email address of the user to search for.", "schema": { "type": "string" } } ], "responses": { "200": { "description": "successful", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/ExtendedUser" } } } } } } }, "/api/users/{id}/unpublish": { "put": { "summary": "Unpublish a User's Articles and Comments", "tags": [ "users" ], "description": "This endpoint allows the client to unpublish all of the articles and comments created by a user.\n\n### Administrative Action:\n- Requires the authenticated user to be an Administrator.\n- This is a destructive administrative action that immediately unpublishes all posts/comments from public feeds.\n- Ideal for handling spam accounts or cleanup operations.", "operationId": "unpublishUser", "parameters": [ { "name": "id", "in": "path", "required": true, "description": "The unique numerical ID of the user whose content will be unpublished.", "schema": { "type": "integer", "format": "int32", "minimum": 1 }, "example": 1 } ], "responses": { "204": { "description": "User's articles and comments successfully unpublished" } } } }, "/api/admin/users": { "post": { "summary": "Invite a User", "tags": [ "users", "admin" ], "description": "Invite a new user to join the platform by email.\n\n### Super Admin Action:\n- Requires Super Admin privileges.\n- Triggers a system invitation flow and sends an invitation email containing a sign-up link.\n- Handy for invite-only platforms or private enterprise instances.", "operationId": "postAdminUsersCreate", "parameters": [ ], "responses": { "200": { "description": "Successful" } }, "requestBody": { "content": { "application/json": { "schema": { "$ref": "#/components/schemas/UserInviteParam" } } }, "description": "User invite params" } }, "get": { "summary": "List all users (Admin)", "tags": [ "users", "admin" ], "description": "Retrieve a list of all users registered on the platform.\n\n### Permissions & Filters:\n- Requires Super Admin privileges.\n- Allows filtering by exact `email` or `username`.\n- Returns paginated list of extended user objects containing email addresses, registration dates, roles, and administrative statuses.", "parameters": [ { "name": "page", "in": "query", "required": false, "description": "Pagination page index.", "schema": { "type": "integer" } }, { "name": "per_page", "in": "query", "required": false, "description": "Number of items to return per page.", "schema": { "type": "integer" } }, { "name": "email", "in": "query", "required": false, "description": "Optional email search filter.", "schema": { "type": "string" } }, { "name": "username", "in": "query", "required": false, "description": "Optional username search filter.", "schema": { "type": "string" } } ], "responses": { "200": { "description": "successful" } } } }, "/api/admin/users/{id}": { "get": { "summary": "Get user detail (Admin)", "tags": [ "users", "admin" ], "description": "Retrieve details of a single user by numerical ID.\n\n### Super Admin Action:\n- Requires Super Admin privileges.\n- Includes administrative settings, audit notes, email newsletter preferences, and OAuth login identity states.", "parameters": [ { "name": "id", "in": "path", "required": true, "description": "Unique user numeric ID.", "schema": { "type": "integer" } } ], "responses": { "200": { "description": "successful" } } }, "patch": { "summary": "Update user profile (Admin)", "tags": [ "users", "admin" ], "description": "Update a user's public profile fields (name, location, bio summary, website) on their behalf. Requires Super Admin credentials.", "parameters": [ { "name": "id", "in": "path", "required": true, "description": "Unique user ID to update.", "schema": { "type": "integer" } } ], "responses": { "200": { "description": "successful" } }, "requestBody": { "content": { "application/json": { "schema": { "type": "object", "properties": { "name": { "type": "string" }, "username": { "type": "string" }, "summary": { "type": "string" }, "location": { "type": "string" }, "website_url": { "type": "string" } } } } }, "description": "User profile updated fields." } } }, "/api/admin/users/{id}/email": { "put": { "summary": "Update user email (Admin)", "tags": [ "users", "admin" ], "description": "Update a user's primary registration email address. Requires Super Admin credentials.", "parameters": [ { "name": "id", "in": "path", "required": true, "description": "Unique user ID to update email for.", "schema": { "type": "integer" } } ], "responses": { "200": { "description": "successful" } }, "requestBody": { "content": { "application/json": { "schema": { "type": "object", "properties": { "email": { "type": "string" } }, "required": [ "email" ] } } }, "description": "Email parameters." } } }, "/api/admin/users/{id}/status": { "put": { "summary": "Update user moderation status (Admin)", "tags": [ "users", "admin" ], "description": "Manually update a user's moderation status.\n\n### Status Details:\n- **status**: Allowed target states (e.g. `active`, `suspended`, `banned`).\n- **note**: Required reason text recorded in the user's moderation log audit history.\n- Requires Super Admin privileges.", "parameters": [ { "name": "id", "in": "path", "required": true, "description": "Unique user ID.", "schema": { "type": "integer" } } ], "responses": { "200": { "description": "successful" } }, "requestBody": { "content": { "application/json": { "schema": { "type": "object", "properties": { "status": { "type": "string" }, "note": { "type": "string" } }, "required": [ "status" ] } } }, "description": "Status parameters." } } }, "/api/admin/users/{id}/notification_settings": { "put": { "summary": "Update user notification settings (Admin)", "tags": [ "users", "admin" ], "description": "Update a user's email notification preferences (e.g., unsubscribing them from the system newsletter). Requires Super Admin credentials.", "parameters": [ { "name": "id", "in": "path", "required": true, "description": "Unique user ID.", "schema": { "type": "integer" } } ], "responses": { "200": { "description": "successful" } }, "requestBody": { "content": { "application/json": { "schema": { "type": "object", "properties": { "notification_setting": { "type": "object", "properties": { "email_newsletter": { "type": "boolean" } } } }, "required": [ "notification_setting" ] } } }, "description": "Settings parameters." } } }, "/api/admin/users/{id}/merge": { "post": { "summary": "Merge user into another (Admin)", "tags": [ "users", "admin" ], "description": "Merge a duplicate user account into a target main account.\n\n### Account Merging Behavior:\n- Transfers all comments, articles, reactions, and follows to the target user (`merge_user_id`).\n- Deletes/destroys the source user account once the merge completes successfully.\n- High risk! Action is permanent and irreversible.\n- Requires Super Admin credentials.", "parameters": [ { "name": "id", "in": "path", "required": true, "description": "The duplicate user ID that will be deleted after contents merge.", "schema": { "type": "integer" } } ], "responses": { "200": { "description": "successful" } }, "requestBody": { "content": { "application/json": { "schema": { "type": "object", "properties": { "merge_user_id": { "type": "integer" } }, "required": [ "merge_user_id" ] } } }, "description": "Merge parameters containing the target account ID." } } }, "/api/admin/users/{user_id}/notes": { "get": { "summary": "List notes for a user (Admin)", "tags": [ "users", "admin" ], "description": "Retrieve all moderator/administrator audit log notes appended to a user. Requires Super Admin credentials.", "parameters": [ { "name": "user_id", "in": "path", "required": true, "description": "User ID to fetch notes for.", "schema": { "type": "integer" } } ], "responses": { "200": { "description": "successful" } } }, "post": { "summary": "Add a note to a user (Admin)", "tags": [ "users", "admin" ], "description": "Add a new moderation/audit note to a user.\n\n### Audit Logging Guidelines:\n- **content**: Plaintext description of behavior, infraction, or actions taken.\n- **reason**: Categorized classification (e.g. `spam`, `abuse`, `harassment`, `administrative`).\n- Requires Super Admin credentials.", "parameters": [ { "name": "user_id", "in": "path", "required": true, "description": "User ID to append the note to.", "schema": { "type": "integer" } } ], "responses": { "201": { "description": "created" } }, "requestBody": { "content": { "application/json": { "schema": { "type": "object", "properties": { "content": { "type": "string" }, "reason": { "type": "string" } }, "required": [ "content" ] } } }, "description": "Note attributes." } } }, "/api/admin/users/{user_id}/identities": { "get": { "summary": "List identities for a user (Admin)", "tags": [ "users", "admin" ], "description": "Retrieve all linked OAuth identities (e.g., GitHub, Twitter, Apple) for a user. Requires Super Admin credentials.", "parameters": [ { "name": "user_id", "in": "path", "required": true, "description": "User ID to fetch linked identities for.", "schema": { "type": "integer" } } ], "responses": { "200": { "description": "successful" } } }, "post": { "summary": "Link an identity to a user (Admin)", "tags": [ "users", "admin" ], "description": "Manually link an OAuth provider identity to a user. Requires Super Admin credentials.\n\n### Identity Binding:\n- **provider**: The login provider name (e.g. `github`, `twitter`).\n- **uid**: The provider's unique user identifier.\n- **username**: The user's username on the provider's service.", "parameters": [ { "name": "user_id", "in": "path", "required": true, "description": "User ID to bind identity to.", "schema": { "type": "integer" } } ], "responses": { "201": { "description": "created" } }, "requestBody": { "content": { "application/json": { "schema": { "type": "object", "properties": { "provider": { "type": "string" }, "uid": { "type": "string" }, "username": { "type": "string" } }, "required": [ "provider", "uid" ] } } }, "description": "OAuth credentials and identities." } } }, "/api/admin/users/{user_id}/identities/{id}": { "delete": { "summary": "Unlink an identity from a user (Admin)", "tags": [ "users", "admin" ], "description": "Unlink a specific OAuth login provider identity from a user by identity ID. Requires Super Admin credentials.", "parameters": [ { "name": "user_id", "in": "path", "required": true, "description": "User ID.", "schema": { "type": "integer" } }, { "name": "id", "in": "path", "required": true, "description": "Identity ID to unlink.", "schema": { "type": "integer" } } ], "responses": { "204": { "description": "no content" } } } }, "/api/admin/users/identities/bulk": { "post": { "summary": "Bulk link identities (Admin)", "tags": [ "users", "admin" ], "description": "Bulk link OAuth identities across multiple users. Requires Super Admin credentials.", "parameters": [ ], "responses": { "200": { "description": "successful" } }, "requestBody": { "content": { "application/json": { "schema": { "type": "object", "properties": { "provider": { "type": "string" }, "identities": { "type": "array", "items": { "type": "object", "properties": { "user_id": { "type": "integer" }, "uid": { "type": "string" } }, "required": [ "user_id", "uid" ] } } }, "required": [ "provider", "identities" ] } } }, "description": "Bulk identity params." } } }, "/api/videos": { "get": { "summary": "Articles with a video", "tags": [ "videos", "articles" ], "security": [ ], "description": "Retrieve a list of articles that contain uploaded videos.\n\n### Videos Overview:\n- Bypasses authentication (can be accessed publicly).\n- Returns articles that are published and include a video asset.\n- Articles are ordered by descending popularity (views, watch time, and reactions).\n- By default, returns 24 video articles per page.", "operationId": "videos", "parameters": [ { "$ref": "#/components/parameters/pageParam" }, { "$ref": "#/components/parameters/perPageParam24to1000" } ], "responses": { "200": { "description": "A List of all articles with videos", "content": { "application/json": { "schema": { "type": "array", "items": { "$ref": "#/components/schemas/VideoArticle" } } } } } } } } }, "servers": [ { "url": "https://dev.to", "description": "Production server" } ], "security": [ { "api-key": [ ] } ], "components": { "securitySchemes": { "api-key": { "type": "apiKey", "name": "api-key", "in": "header", "description": "API Key authentication.\n\nAuthentication for some endpoints, like write operations on the\nArticles API require a DEV API key.\n\nAll authenticated endpoints are CORS disabled, the API key is intended for non-browser scripts.\n\n### Getting an API key\n\nTo obtain one, please follow these steps:\n\n - visit https://dev.to/settings/extensions\n - in the \"DEV API Keys\" section create a new key by adding a\n description and clicking on \"Generate API Key\"\n\n ![obtain a DEV API Key](https://user-images.githubusercontent.com/37842/172718105-bd93664e-76e0-477d-99c4-265dda0b06c5.png)\n\n - You'll see the newly generated key in the same view\n ![generated DEV API Key](https://user-images.githubusercontent.com/37842/172718151-e7fe26a0-9937-42e8-96c6-333acdab9e49.png)" } }, "parameters": { "pageParam": { "in": "query", "name": "page", "required": false, "description": "Pagination page", "schema": { "type": "integer", "format": "int32", "minimum": 1, "default": 1 } }, "perPageParam10to1000": { "in": "query", "name": "per_page", "required": false, "description": "Page size (the number of items to return per page). The default maximum value can be overridden by \"API_PER_PAGE_MAX\" environment variable.", "schema": { "type": "integer", "format": "int32", "minimum": 1, "maximum": 1000, "default": 10 } }, "perPageParam24to1000": { "in": "query", "name": "per_page", "required": false, "description": "Page size (the number of items to return per page). The default maximum value can be overridden by \"API_PER_PAGE_MAX\" environment variable.", "schema": { "type": "integer", "format": "int32", "minimum": 1, "maximum": 1000, "default": 24 } }, "perPageParam30to1000": { "in": "query", "name": "per_page", "required": false, "description": "Page size (the number of items to return per page). The default maximum value can be overridden by \"API_PER_PAGE_MAX\" environment variable.", "schema": { "type": "integer", "format": "int32", "minimum": 1, "maximum": 1000, "default": 30 } }, "perPageParam30to100": { "in": "query", "name": "per_page", "required": false, "description": "Page size (the number of items to return per page). The default maximum value can be overridden by \"API_PER_PAGE_MAX\" environment variable.", "schema": { "type": "integer", "format": "int32", "minimum": 1, "maximum": 100, "default": 30 } }, "perPageParam80to1000": { "in": "query", "name": "per_page", "required": false, "description": "Page size (the number of items to return per page). The default maximum value can be overridden by \"API_PER_PAGE_MAX\" environment variable.", "schema": { "type": "integer", "format": "int32", "minimum": 1, "maximum": 1000, "default": 80 } }, "listingCategoryParam": { "name": "category", "in": "query", "description": "Using this parameter will return listings belonging to the\n requested category.", "schema": { "type": "string" }, "example": "cfp" } }, "schemas": { "ArticleFlareTag": { "description": "Flare tag of the article", "type": "object", "properties": { "name": { "type": "string" }, "bg_color_hex": { "description": "Background color (hexadecimal)", "type": "string", "nullable": true }, "text_color_hex": { "description": "Text color (hexadecimal)", "type": "string", "nullable": true } } }, "ArticleIndex": { "description": "Representation of an article or post returned in a list", "type": "object", "properties": { "type_of": { "type": "string" }, "id": { "type": "integer", "format": "int32" }, "title": { "type": "string" }, "description": { "type": "string" }, "cover_image": { "type": "string", "format": "url", "nullable": true }, "readable_publish_date": { "type": "string" }, "social_image": { "type": "string", "format": "url" }, "tag_list": { "type": "array", "items": { "type": "string" } }, "tags": { "type": "string" }, "slug": { "type": "string" }, "path": { "type": "string", "format": "path" }, "url": { "type": "string", "format": "url" }, "canonical_url": { "type": "string", "format": "url" }, "positive_reactions_count": { "type": "integer", "format": "int32" }, "public_reactions_count": { "type": "integer", "format": "int32" }, "created_at": { "type": "string", "format": "date-time" }, "edited_at": { "type": "string", "format": "date-time", "nullable": true }, "crossposted_at": { "type": "string", "format": "date-time", "nullable": true }, "published_at": { "type": "string", "format": "date-time" }, "last_comment_at": { "type": "string", "format": "date-time" }, "published_timestamp": { "description": "Crossposting or published date time", "type": "string", "format": "date-time" }, "reading_time_minutes": { "description": "Reading time, in minutes", "type": "integer", "format": "int32" }, "user": { "$ref": "#/components/schemas/SharedUser" }, "flare_tag": { "$ref": "#/components/schemas/ArticleFlareTag" }, "organization": { "$ref": "#/components/schemas/SharedOrganization" } }, "required": [ "type_of", "id", "title", "description", "cover_image", "readable_publish_date", "social_image", "tag_list", "tags", "slug", "path", "url", "canonical_url", "comments_count", "positive_reactions_count", "public_reactions_count", "created_at", "edited_at", "crossposted_at", "published_at", "last_comment_at", "published_timestamp", "user", "reading_time_minutes" ] }, "VideoArticle": { "description": "Representation of an Article with video", "type": "object", "properties": { "type_of": { "type": "string" }, "id": { "type": "integer", "format": "int64" }, "path": { "type": "string" }, "cloudinary_video_url": { "type": "string" }, "title": { "type": "string" }, "user_id": { "type": "integer", "format": "int64" }, "video_duration_in_minutes": { "type": "string" }, "video_source_url": { "type": "string" }, "user": { "description": "Author of the article", "type": "object", "properties": { "name": { "type": "string" } } } } }, "Article": { "description": "Representation of an Article to be created/updated", "type": "object", "properties": { "article": { "type": "object", "properties": { "title": { "type": "string" }, "body_markdown": { "type": "string" }, "published": { "type": "boolean", "default": false }, "series": { "type": "string", "nullable": true }, "main_image": { "type": "string", "nullable": true }, "canonical_url": { "type": "string", "nullable": true }, "description": { "type": "string" }, "tags": { "type": "string" }, "organization_id": { "type": "integer", "nullable": true } } } } }, "Organization": { "description": "Representation of an Organization", "type": "object", "properties": { "type_of": { "type": "string" }, "username": { "type": "string" }, "name": { "type": "string" }, "summary": { "type": "string" }, "twitter_username": { "type": "string" }, "github_username": { "type": "string" }, "url": { "type": "string" }, "location": { "type": "string" }, "joined_at": { "type": "string" }, "tech_stack": { "type": "string" }, "tag_line": { "type": "string", "nullable": true }, "story": { "type": "string", "nullable": true } } }, "FollowedTag": { "description": "Representation of a followed tag", "type": "object", "properties": { "id": { "description": "Tag id", "type": "integer", "format": "int64" }, "name": { "type": "string" }, "points": { "type": "number", "format": "float" } }, "required": [ "id", "name", "points" ] }, "Tag": { "description": "Representation of a tag", "type": "object", "properties": { "id": { "description": "Tag id", "type": "integer", "format": "int64" }, "name": { "type": "string" }, "bg_color_hex": { "type": "string", "nullable": true }, "text_color_hex": { "type": "string", "nullable": true } } }, "Page": { "description": "Representation of a page object", "type": "object", "properties": { "title": { "type": "string", "description": "Title of the page" }, "slug": { "type": "string", "description": "Used to link to this page in URLs, must be unique and URL-safe" }, "description": { "type": "string", "description": "For internal use, helps similar pages from one another" }, "body_markdown": { "type": "string", "description": "The text (in markdown) of the ad (required)", "nullable": true }, "body_json": { "type": "string", "description": "For JSON pages, the JSON body", "nullable": true }, "is_top_level_path": { "type": "boolean", "description": "If true, the page is available at '/{slug}' instead of '/page/{slug}', use with caution" }, "social_image": { "type": "object", "nullable": true }, "template": { "type": "string", "enum": [ "contained", "full_within_layout", "nav_bar_included", "json", "css", "txt" ], "default": "contained", "description": "Controls what kind of layout the page is rendered in" } }, "required": [ "title", "slug", "description", "template" ] }, "PodcastEpisodeIndex": { "description": "Representation of a podcast episode returned in a list", "type": "object", "properties": { "type_of": { "type": "string" }, "id": { "type": "integer", "format": "int32" }, "class_name": { "type": "string" }, "path": { "type": "string", "format": "path" }, "title": { "type": "string" }, "image_url": { "description": "Podcast episode image url or podcast image url", "type": "string", "format": "url" }, "podcast": { "$ref": "#/components/schemas/SharedPodcast" } }, "required": [ "type_of", "class_name", "id", "path", "title", "image_url", "podcast" ] }, "ProfileImage": { "description": "A profile image object", "type": "object", "properties": { "type_of": { "description": "Return profile_image", "type": "string" }, "image_of": { "description": "Determines the type of the profile image owner (user or organization)", "type": "string" }, "profile_image": { "description": "Profile image (640x640)", "type": "string" }, "profile_image_90": { "description": "Profile image (90x90)", "type": "string" } } }, "SharedUser": { "description": "The resource creator", "type": "object", "properties": { "name": { "type": "string" }, "username": { "type": "string" }, "twitter_username": { "type": "string", "nullable": true }, "github_username": { "type": "string", "nullable": true }, "website_url": { "type": "string", "format": "url", "nullable": true }, "profile_image": { "description": "Profile image (640x640)", "type": "string" }, "profile_image_90": { "description": "Profile image (90x90)", "type": "string" } } }, "SharedOrganization": { "description": "The organization the resource belongs to", "type": "object", "properties": { "name": { "type": "string" }, "username": { "type": "string" }, "slug": { "type": "string" }, "profile_image": { "description": "Profile image (640x640)", "type": "string", "format": "url" }, "profile_image_90": { "description": "Profile image (90x90)", "type": "string", "format": "url" } } }, "User": { "description": "The representation of a user returned in a list", "type": "object", "properties": { "type_of": { "type": "string" }, "id": { "type": "integer", "format": "int64" }, "username": { "type": "string" }, "name": { "type": "string" }, "summary": { "type": "string", "nullable": true }, "twitter_username": { "type": "string" }, "github_username": { "type": "string" }, "website_url": { "type": "string", "nullable": true }, "location": { "type": "string", "nullable": true }, "joined_at": { "type": "string" }, "profile_image": { "type": "string" } } }, "ExtendedUser": { "description": "The representation of a user", "type": "object", "properties": { "type_of": { "type": "string" }, "id": { "type": "integer", "format": "int64" }, "username": { "type": "string" }, "name": { "type": "string" }, "summary": { "type": "string", "nullable": true }, "twitter_username": { "type": "string" }, "github_username": { "type": "string" }, "email": { "type": "string", "nullable": true, "description": "Email (if user allows displaying email on their profile) or nil" }, "website_url": { "type": "string", "nullable": true }, "location": { "type": "string", "nullable": true }, "joined_at": { "type": "string" }, "profile_image": { "type": "string" }, "badge_ids": { "type": "array", "items": { "type": "integer" }, "description": "ids of the badges awarded to the user" } } }, "MyUser": { "description": "The representation of a user when accessed by themselves", "type": "object", "properties": { "type_of": { "type": "string" }, "id": { "type": "integer", "format": "int64" }, "username": { "type": "string" }, "name": { "type": "string" }, "summary": { "type": "string", "nullable": true }, "twitter_username": { "type": "string" }, "github_username": { "type": "string" }, "email": { "type": "string", "nullable": true, "description": "Email (if user allows displaying email on their profile) or nil" }, "website_url": { "type": "string", "nullable": true }, "location": { "type": "string", "nullable": true }, "joined_at": { "type": "string" }, "profile_image": { "type": "string" }, "badge_ids": { "type": "array", "items": { "type": "integer" }, "description": "ids of the badges awarded to the user" }, "followers_count": { "type": "integer" } } }, "SharedPodcast": { "description": "The podcast that the resource belongs to", "type": "object", "properties": { "title": { "type": "string" }, "slug": { "type": "string" }, "image_url": { "description": "Podcast image url", "type": "string", "format": "url" } } }, "Comment": { "description": "A Comment on an Article or Podcast Episode", "type": "object", "properties": { "type_of": { "type": "string" }, "id_code": { "type": "string" }, "created_at": { "type": "string", "format": "date-time" }, "image_url": { "description": "Podcast image url", "type": "string", "format": "url" } } }, "UserInviteParam": { "description": "User invite parameters", "type": "object", "properties": { "email": { "type": "string" }, "name": { "type": "string", "nullable": true } } }, "Billboard": { "description": "Billboard, aka Widget, ex. Display Ad", "type": "object", "properties": { "id": { "type": "integer", "description": "The ID of the Billboard" }, "name": { "type": "string", "description": "For internal use, helps distinguish ads from one another" }, "body_markdown": { "type": "string", "description": "The text (in markdown) of the ad (required)" }, "approved": { "type": "boolean", "description": "Ad must be both published and approved to be in rotation" }, "published": { "type": "boolean", "description": "Ad must be both published and approved to be in rotation" }, "expires_at": { "type": "string", "format": "date-time", "nullable": true, "description": "Timestamp when the billboard expires. After this time, the billboard will automatically be marked as not approved." }, "organization_id": { "type": "integer", "description": "Identifies the organization to which the ad belongs", "nullable": true }, "creator_id": { "type": "integer", "description": "Identifies the user who created the ad.", "nullable": true }, "placement_area": { "type": "string", "enum": [ "sidebar_left", "sidebar_left_2", "sidebar_right", "sidebar_right_second", "sidebar_right_third", "feed_first", "feed_second", "feed_third", "home_hero", "footer", "page_fixed_bottom", "post_fixed_bottom", "post_body_bottom", "post_sidebar", "post_comments", "post_comments_mid", "digest_first", "digest_second" ], "description": "Identifies which area of site layout the ad can appear in" }, "tag_list": { "type": "string", "description": "Tags on which this ad can be displayed (blank is all/any tags)" }, "exclude_article_ids": { "type": "string", "nullable": true, "description": "Articles this ad should *not* appear on (blank means no articles are disallowed, and this ad can appear next to any/all articles). Comma-separated list of integer Article IDs" }, "audience_segment_id": { "type": "integer", "description": "Specifies a specific audience segment who will see this billboard" }, "audience_segment_type": { "type": "string", "enum": [ "manual", "trusted", "posted", "no_posts_yet", "dark_theme", "light_theme", "no_experience", "experience1", "experience2", "experience3", "experience4", "experience5" ], "description": "Specifies a group of users who will see this billboard (must match audience_segment_id if both provided)" }, "target_geolocations": { "type": "array", "items": { "type": "string" }, "description": "Locations to show this billboard in (blank means it will be shown in all locations). Specified as a comma-separated list or array of ISO 3166-2 country and optionally region codes)" }, "display_to": { "type": "string", "enum": [ "all", "logged_in", "logged_out" ], "default": "all", "description": "Potentially limits visitors to whom the ad is visible" }, "type_of": { "type": "string", "enum": [ "in_house", "community", "external" ], "default": "in_house", "description": "Types of the billboards:\nin_house (created by admins),\ncommunity (created by an entity, appears on entity's content),\nexternal ( created by an entity, or a non-entity, can appear everywhere)\n" } }, "required": [ "name", "body_markdown", "placement_area" ] }, "Segment": { "description": "A manually managed audience segment", "type": "object", "properties": { "id": { "type": "integer", "description": "The ID of the segment" }, "type_of": { "type": "string", "enum": [ "manual" ], "default": "manual", "description": "Marks the segment as manually managed (other types are internal)" }, "user_count": { "type": "integer", "description": "The current number of users in the segment" } } }, "SegmentUserIds": { "type": "object", "properties": { "user_ids": { "type": "array", "items": { "type": "integer" }, "maxItems": 10000 } } }, "AgentSessionIndex": { "description": "Representation of an agent session returned in a list or after creation", "type": "object", "properties": { "id": { "type": "integer", "format": "int64" }, "slug": { "type": "string" }, "title": { "type": "string" }, "tool_name": { "type": "string", "description": "Tool that produced the session (e.g. claude_code, codex)" }, "total_messages": { "type": "integer", "format": "int32" }, "published": { "type": "boolean" }, "created_at": { "type": "string", "format": "date-time" }, "updated_at": { "type": "string", "format": "date-time" }, "url": { "type": "string", "format": "url" } }, "required": [ "id", "slug", "title", "tool_name", "total_messages", "published", "created_at", "url" ] }, "AgentSessionShow": { "description": "Full representation of an agent session including messages and curation data", "type": "object", "properties": { "id": { "type": "integer", "format": "int64" }, "slug": { "type": "string" }, "title": { "type": "string" }, "tool_name": { "type": "string", "description": "Tool that produced the session (e.g. claude_code, codex)" }, "total_messages": { "type": "integer", "format": "int32" }, "curated_count": { "type": "integer", "format": "int32", "description": "Number of curated messages selected for display" }, "published": { "type": "boolean" }, "metadata": { "type": "object", "nullable": true, "description": "Session metadata (tool-specific)" }, "messages": { "type": "array", "items": { "type": "object" }, "description": "All normalized messages in the session" }, "slices": { "type": "array", "items": { "type": "object" }, "description": "Named slices grouping message ranges" }, "created_at": { "type": "string", "format": "date-time" }, "updated_at": { "type": "string", "format": "date-time" }, "url": { "type": "string", "format": "url" } }, "required": [ "id", "slug", "title", "tool_name", "total_messages", "curated_count", "published", "messages", "slices", "created_at", "updated_at", "url" ] }, "PollOption": { "description": "A single option within a poll", "type": "object", "properties": { "type_of": { "type": "string", "enum": [ "poll_option" ], "description": "Resource discriminator" }, "id": { "type": "integer", "format": "int64" }, "markdown": { "type": "string", "nullable": true, "description": "Option text in markdown" }, "processed_html": { "type": "string", "nullable": true, "description": "Option text rendered as HTML" }, "position": { "type": "integer", "format": "int32", "description": "Display order within the poll" }, "poll_votes_count": { "type": "integer", "format": "int32", "description": "Number of votes for this option" }, "supplementary_text": { "type": "string", "nullable": true, "description": "Additional descriptive text for the option" } }, "required": [ "type_of", "id", "markdown", "processed_html", "position", "poll_votes_count" ] }, "Poll": { "description": "A poll (question) belonging to a survey or article", "type": "object", "properties": { "type_of": { "type": "string", "enum": [ "poll" ], "description": "Resource discriminator" }, "id": { "type": "integer", "format": "int64" }, "prompt_markdown": { "type": "string", "nullable": true, "description": "Question text in markdown" }, "prompt_html": { "type": "string", "nullable": true, "description": "Question text rendered as HTML" }, "poll_type_of": { "type": "string", "enum": [ "single_choice", "multiple_choice", "scale", "text_input" ], "description": "Poll question type: single_choice, multiple_choice, scale, or text_input" }, "position": { "type": "integer", "format": "int32", "description": "Display order within the survey" }, "poll_votes_count": { "type": "integer", "format": "int32", "description": "Total number of votes across all options" }, "poll_skips_count": { "type": "integer", "format": "int32", "description": "Number of users who skipped this poll" }, "poll_options_count": { "type": "integer", "format": "int32", "description": "Number of options in this poll" }, "scale_min": { "type": "integer", "format": "int32", "nullable": true, "description": "Minimum value for scale polls" }, "scale_max": { "type": "integer", "format": "int32", "nullable": true, "description": "Maximum value for scale polls" }, "created_at": { "type": "string", "format": "date-time" }, "updated_at": { "type": "string", "format": "date-time" }, "poll_options": { "type": "array", "items": { "$ref": "#/components/schemas/PollOption" }, "description": "The available options for this poll" } }, "required": [ "type_of", "id", "prompt_markdown", "prompt_html", "poll_type_of", "position", "poll_votes_count", "poll_skips_count", "poll_options_count", "created_at", "updated_at", "poll_options" ] }, "Survey": { "description": "Representation of a survey", "type": "object", "properties": { "type_of": { "type": "string", "enum": [ "survey" ], "description": "Resource discriminator" }, "id": { "type": "integer", "format": "int64" }, "title": { "type": "string" }, "slug": { "type": "string" }, "survey_type_of": { "type": "string", "enum": [ "community_pulse", "industry", "fun" ], "description": "Survey category" }, "active": { "type": "boolean", "nullable": true, "description": "Whether the survey is currently active" }, "display_title": { "type": "boolean", "description": "Whether to show the title to respondents" }, "allow_resubmission": { "type": "boolean", "description": "Whether users can submit multiple times" }, "daily_email_distributions": { "type": "integer", "format": "int32", "description": "Daily email distributions count" }, "extra_email_context_paragraph": { "type": "string", "nullable": true, "description": "Optional context paragraph for emails" }, "target_response_count": { "type": "integer", "format": "int32", "description": "Target response count" }, "target_completion_date": { "type": "string", "format": "date-time", "nullable": true, "description": "Target completion date in the future" }, "created_at": { "type": "string", "format": "date-time" }, "updated_at": { "type": "string", "format": "date-time" } }, "required": [ "type_of", "id", "title", "slug", "survey_type_of", "display_title", "allow_resubmission", "created_at", "updated_at" ] }, "SurveyWithPolls": { "description": "Representation of a survey including its polls and poll options", "allOf": [ { "$ref": "#/components/schemas/Survey" }, { "type": "object", "properties": { "polls": { "type": "array", "items": { "$ref": "#/components/schemas/Poll" }, "description": "All polls in the survey, ordered by position" } }, "required": [ "polls" ] } ] }, "SurveyInput": { "description": "Parameters for creating or updating a survey", "type": "object", "properties": { "survey": { "type": "object", "properties": { "title": { "type": "string", "description": "Title of the survey" }, "survey_type_of": { "type": "string", "enum": [ "community_pulse", "industry", "fun" ], "description": "Survey category" }, "type_of": { "type": "string", "enum": [ "community_pulse", "industry", "fun" ], "description": "Survey category (alias of survey_type_of)" }, "active": { "type": "boolean", "description": "Whether the survey is active" }, "display_title": { "type": "boolean", "description": "Whether to show the title to respondents" }, "allow_resubmission": { "type": "boolean", "description": "Whether users can submit multiple times" }, "daily_email_distributions": { "type": "integer", "format": "int32", "description": "Daily email distributions count" }, "extra_email_context_paragraph": { "type": "string", "nullable": true, "description": "Optional context paragraph for emails" }, "target_response_count": { "type": "integer", "format": "int32", "description": "Target response count" }, "target_completion_date": { "type": "string", "format": "date-time", "nullable": true, "description": "Target completion date in the future" }, "polls": { "type": "array", "items": { "type": "object", "properties": { "id": { "type": "integer", "format": "int64", "description": "ID of the poll to update, omit for new polls" }, "prompt_markdown": { "type": "string", "description": "Question text in markdown" }, "poll_type_of": { "type": "string", "enum": [ "single_choice", "multiple_choice", "scale", "text_input" ], "description": "Poll type" }, "type_of": { "type": "string", "enum": [ "single_choice", "multiple_choice", "scale", "text_input" ], "description": "Poll type (alias of poll_type_of)" }, "position": { "type": "integer", "format": "int32", "description": "Display order within survey" }, "scale_min": { "type": "integer", "format": "int32", "description": "Minimum value for scale polls" }, "scale_max": { "type": "integer", "format": "int32", "description": "Maximum value for scale polls" }, "_destroy": { "type": "boolean", "description": "Set to true to destroy this poll" }, "poll_options": { "type": "array", "items": { "type": "object", "properties": { "id": { "type": "integer", "format": "int64", "description": "ID of the option to update, omit for new options" }, "markdown": { "type": "string", "description": "Option markdown text" }, "supplementary_text": { "type": "string", "description": "Optional supplementary text" }, "position": { "type": "integer", "format": "int32", "description": "Display order within poll" }, "_destroy": { "type": "boolean", "description": "Set to true to destroy this option" } } } } } } } } } }, "required": [ "survey" ] }, "PollVote": { "description": "Representation of a single poll vote cast by a user", "type": "object", "properties": { "type_of": { "type": "string", "enum": [ "poll_vote" ], "description": "Resource discriminator" }, "id": { "type": "integer", "format": "int64" }, "poll_id": { "type": "integer", "format": "int64" }, "poll_option_id": { "type": "integer", "format": "int64" }, "user_id": { "type": "integer", "format": "int64" }, "user_email": { "type": "string", "format": "email" }, "session_start": { "type": "integer", "format": "int32" }, "created_at": { "type": "string", "format": "date-time" } }, "required": [ "type_of", "id", "poll_id", "poll_option_id", "user_id", "user_email", "session_start", "created_at" ] }, "PollTextResponse": { "description": "Representation of a free-text response to a text-input poll", "type": "object", "properties": { "type_of": { "type": "string", "enum": [ "poll_text_response" ], "description": "Resource discriminator" }, "id": { "type": "integer", "format": "int64" }, "poll_id": { "type": "integer", "format": "int64" }, "user_id": { "type": "integer", "format": "int64" }, "user_email": { "type": "string", "format": "email" }, "text_content": { "type": "string" }, "session_start": { "type": "integer", "format": "int32" }, "created_at": { "type": "string", "format": "date-time" } }, "required": [ "type_of", "id", "poll_id", "user_id", "user_email", "text_content", "session_start", "created_at" ] }, "Trend": { "description": "Representation of a trend", "type": "object", "properties": { "type_of": { "type": "string" }, "id": { "type": "integer", "format": "int64" }, "name": { "type": "string" }, "slug": { "type": "string" }, "description": { "type": "string", "nullable": true }, "key_questions": { "type": "array", "items": { "type": "string" } }, "score": { "type": "number", "format": "float" }, "articles_count": { "type": "integer", "format": "int32" }, "cover_image": { "type": "string", "format": "url", "nullable": true }, "first_observed_at": { "type": "string", "format": "date-time" }, "last_observed_at": { "type": "string", "format": "date-time" }, "created_at": { "type": "string", "format": "date-time" }, "updated_at": { "type": "string", "format": "date-time" } }, "required": [ "type_of", "id", "name", "slug", "key_questions", "score", "articles_count", "first_observed_at", "last_observed_at", "created_at", "updated_at" ] }, "Concept": { "description": "Representation of a concept", "type": "object", "properties": { "id": { "type": "integer", "format": "int64" }, "name": { "type": "string" }, "slug": { "type": "string" }, "description": { "type": "string", "nullable": true }, "parent_id": { "type": "integer", "format": "int64", "nullable": true }, "score": { "type": "number", "format": "float" }, "similarity_threshold": { "type": "number", "format": "float", "nullable": true }, "created_at": { "type": "string", "format": "date-time" }, "updated_at": { "type": "string", "format": "date-time" }, "daily_metrics": { "type": "array", "items": { "$ref": "#/components/schemas/ConceptDailyMetric" } }, "top_articles": { "type": "array", "items": { "type": "object", "properties": { "id": { "type": "integer", "format": "int64" }, "title": { "type": "string" }, "slug": { "type": "string" }, "score": { "type": "number", "format": "float" }, "published_at": { "type": "string", "format": "date-time" } } } } }, "required": [ "id", "name", "slug", "created_at", "updated_at" ] }, "ConceptDailyMetric": { "description": "Representation of daily metrics for a concept", "type": "object", "properties": { "date": { "type": "string", "format": "date" }, "articles_count": { "type": "integer" }, "comments_count": { "type": "integer" }, "page_views": { "type": "integer" }, "reactions_count": { "type": "integer" }, "popularity_score": { "type": "number", "format": "float" } }, "required": [ "date", "articles_count", "comments_count", "page_views", "reactions_count", "popularity_score" ] }, "RequestRedirect": { "description": "Representation of a request redirect", "type": "object", "properties": { "id": { "type": "integer", "format": "int64" }, "original_url": { "type": "string" }, "destination_url": { "type": "string" }, "request_domain": { "type": "string" }, "created_at": { "type": "string", "format": "date-time" }, "updated_at": { "type": "string", "format": "date-time" } }, "required": [ "id", "original_url", "destination_url", "request_domain", "created_at", "updated_at" ] }, "Badge": { "description": "Representation of a badge", "type": "object", "properties": { "id": { "type": "integer", "format": "int64" }, "title": { "type": "string" }, "slug": { "type": "string" }, "description": { "type": "string" }, "badge_image": { "type": "object", "properties": { "url": { "type": "string" } }, "nullable": true }, "credits_awarded": { "type": "integer" }, "allow_multiple_awards": { "type": "boolean" }, "created_at": { "type": "string", "format": "date-time" }, "updated_at": { "type": "string", "format": "date-time" } }, "required": [ "id", "title", "slug", "description", "created_at", "updated_at" ] }, "BadgeAchievement": { "description": "Representation of a badge achievement", "type": "object", "properties": { "id": { "type": "integer", "format": "int64" }, "user_id": { "type": "integer", "format": "int64" }, "badge_id": { "type": "integer", "format": "int64" }, "rewarding_context_message_markdown": { "type": "string", "nullable": true }, "include_default_description": { "type": "boolean" }, "created_at": { "type": "string", "format": "date-time" }, "updated_at": { "type": "string", "format": "date-time" } }, "required": [ "id", "user_id", "badge_id", "created_at", "updated_at" ] }, "RecommendedArticlesList": { "description": "Representation of a recommended articles list", "type": "object", "properties": { "id": { "type": "integer", "format": "int64" }, "name": { "type": "string" }, "placement_area": { "type": "string" }, "expires_at": { "type": "string", "format": "date-time", "nullable": true }, "user_id": { "type": "integer", "format": "int64" }, "article_ids": { "type": "array", "items": { "type": "integer" } }, "created_at": { "type": "string", "format": "date-time" }, "updated_at": { "type": "string", "format": "date-time" } }, "required": [ "id", "name", "placement_area", "user_id", "article_ids", "created_at", "updated_at" ] }, "Subforem": { "description": "Representation of a subforem", "type": "object", "properties": { "id": { "type": "integer", "format": "int64" }, "domain": { "type": "string" }, "root": { "type": "boolean" }, "name": { "type": "string" }, "description": { "type": "string" }, "logo_image_url": { "type": "string" }, "cover_image_url": { "type": "string" } }, "required": [ "id", "domain", "root", "name", "description", "logo_image_url", "cover_image_url" ] } } } }