@lap v0.3
# Machine-readable API spec. Each @endpoint block is one API call.
@api Forem API V1
@base https://dev.to
@version 1.0.0
@auth ApiKey api-key in header
@endpoints 131
@hint download_for_search
@toc api(131)

@endpoint GET /api/agent_sessions
@returns(200)
@errors {401}

@endpoint POST /api/agent_sessions
@required {curated_data: str}
@optional {title: str, s3_key: str, tool_name: str(claude_code/codex/gemini_cli/github_copilot/opencode/pi)}
@returns(201) {id: int(int64), slug: str, title: str, tool_name: str, total_messages: int(int32), published: bool, created_at: str(date-time), updated_at: str(date-time), url: str(url)}
@errors {401, 422}

@endpoint GET /api/agent_sessions/{id}
@required {id: str}
@returns(200) {id: int(int64), slug: str, title: str, tool_name: str, total_messages: int(int32), curated_count: int(int32), published: bool, metadata: map?, messages: [map], slices: [map], created_at: str(date-time), updated_at: str(date-time), url: str(url)}
@errors {401, 404}

@endpoint GET /api/analytics/totals
@optional {article_id: int, organization_id: int}
@returns(200)

@endpoint GET /api/analytics/historical
@required {start: str}
@optional {end: str, article_id: int, organization_id: int}
@returns(200)

@endpoint GET /api/analytics/past_day
@optional {article_id: int, organization_id: int}
@returns(200)

@endpoint GET /api/analytics/referrers
@optional {start: str, end: str, article_id: int, organization_id: int}
@returns(200)

@endpoint GET /api/analytics/top_contributors
@optional {start: str, end: str, article_id: int, organization_id: int}
@returns(200)

@endpoint GET /api/analytics/follower_engagement
@optional {start: str, end: str}
@returns(200)

@endpoint GET /api/analytics/dashboard
@optional {start: str, end: str, article_id: int, organization_id: int}
@returns(200)

@endpoint GET /api/analytics/heatmap
@optional {end: str}
@returns(200)

@endpoint POST /api/articles
@optional {article: map{title: str, body_markdown: str, published: bool, series: str, main_image: str, canonical_url: str, description: str, tags: str, organization_id: int, ai_disclosure_level: str}}
@returns(201)
@errors {401, 422}

@endpoint GET /api/articles
@optional {page: int(int32)=1, per_page: int(int32)=30, tag: str, tags: str, tags_exclude: str, username: str, state: str(fresh/rising/all), top: int(int32), collection_id: int(int32)}
@returns(200)

@endpoint GET /api/articles/search
@optional {q: str, top: int, page: int, per_page: int}
@returns(200)

@endpoint GET /api/articles/latest
@optional {page: int(int32)=1, per_page: int(int32)=30}
@returns(200)

@endpoint GET /api/articles/{id}
@required {id: int}
@returns(200)
@errors {404}

@endpoint PUT /api/articles/{id}
@required {id: int(int32)}
@optional {article: map{title: str, body_markdown: str, published: bool, series: str, main_image: str, canonical_url: str, description: str, tags: str, organization_id: int, ai_disclosure_level: str}}
@returns(200)
@errors {401, 404, 422}

@endpoint GET /api/articles/{username}/{slug}
@required {username: str, slug: str}
@returns(200)
@errors {404}

@endpoint GET /api/articles/me
@optional {page: int(int32)=1, per_page: int(int32)=30}
@returns(200)
@errors {401}

@endpoint GET /api/articles/me/published
@optional {page: int(int32)=1, per_page: int(int32)=30}
@returns(200)
@errors {401}

@endpoint GET /api/articles/me/unpublished
@optional {page: int(int32)=1, per_page: int(int32)=30}
@returns(200)
@errors {401}

@endpoint GET /api/articles/me/all
@optional {page: int(int32)=1, per_page: int(int32)=30}
@returns(200)
@errors {401}

@endpoint PUT /api/articles/{id}/unpublish
@required {id: int(int32)}
@optional {note: str}
@returns(204)
@errors {401, 404}

@endpoint GET /api/articles/semantic_search
@required {q: str}
@optional {per_page: int, page: int, threshold: num}
@returns(200)
@errors {400, 401}

@endpoint GET /api/segments
@optional {per_page: int(int32)=30}
@returns(200)
@errors {401}

@endpoint POST /api/segments
@returns(201)
@errors {401}

@endpoint GET /api/segments/{id}
@required {id: int(int32)}
@returns(200)
@errors {401, 404}

@endpoint DELETE /api/segments/{id}
@required {id: int(int32)}
@returns(200)
@errors {401, 404, 409}

@endpoint GET /api/segments/{id}/users
@required {id: int(int32)}
@optional {per_page: int(int32)=30}
@returns(200)
@errors {401, 404}

@endpoint PUT /api/segments/{id}/add_users
@required {id: int(int32)}
@optional {user_ids: [int]}
@returns(200)
@errors {401, 404, 422}

@endpoint PUT /api/segments/{id}/remove_users
@required {id: int(int32)}
@optional {user_ids: [int]}
@returns(200)
@errors {401, 404, 422}

@endpoint GET /api/badge_achievements
@optional {page: int}
@returns(200)

@endpoint POST /api/badge_achievements
@optional {badge_achievement: map{user_id!: int, badge_id!: int, rewarding_context_message_markdown: str, include_default_description: bool}}
@returns(201) {id: int(int64), user_id: int(int64), badge_id: int(int64), rewarding_context_message_markdown: str?, include_default_description: bool, created_at: str(date-time), updated_at: str(date-time)}

@endpoint GET /api/badge_achievements/{id}
@required {id: int}
@returns(200) {id: int(int64), user_id: int(int64), badge_id: int(int64), rewarding_context_message_markdown: str?, include_default_description: bool, created_at: str(date-time), updated_at: str(date-time)}

@endpoint DELETE /api/badge_achievements/{id}
@required {id: int}
@returns(204)

@endpoint GET /api/badges
@optional {page: int}
@returns(200)

@endpoint POST /api/badges
@optional {badge: map{title!: str, description!: str, remote_badge_image_url!: str, credits_awarded: int, allow_multiple_awards: bool}}
@returns(201) {id: int(int64), title: str, slug: str, description: str, badge_image: map?{url: str}, credits_awarded: int, allow_multiple_awards: bool, created_at: str(date-time), updated_at: str(date-time)}

@endpoint GET /api/badges/{id}
@required {id: int}
@returns(200) {id: int(int64), title: str, slug: str, description: str, badge_image: map?{url: str}, credits_awarded: int, allow_multiple_awards: bool, created_at: str(date-time), updated_at: str(date-time)}

@endpoint PATCH /api/badges/{id}
@required {id: int}
@optional {badge: map{title: str, description: str, credits_awarded: int, allow_multiple_awards: bool}}
@returns(200) {id: int(int64), title: str, slug: str, description: str, badge_image: map?{url: str}, credits_awarded: int, allow_multiple_awards: bool, created_at: str(date-time), updated_at: str(date-time)}

@endpoint DELETE /api/badges/{id}
@required {id: int}
@returns(204)

@endpoint GET /api/billboards
@returns(200)
@errors {401}

@endpoint POST /api/billboards
@returns(201)
@errors {401, 422}

@endpoint GET /api/billboards/{id}
@required {id: int(int32)}
@returns(200)
@errors {401, 404}

@endpoint PUT /api/billboards/{id}
@required {id: int(int32)}
@returns(200)
@errors {401, 404}

@endpoint PUT /api/billboards/{id}/unpublish
@required {id: int(int32)}
@returns(204)
@errors {401, 404}

@endpoint GET /api/comments
@optional {page: int(int32)=1, per_page: int(int32)=30, a_id: str, p_id: str, page: str}
@returns(200)
@errors {404}

@endpoint GET /api/comments/{id}
@required {id: str}
@returns(200)
@errors {404}

@endpoint GET /api/concepts
@optional {page: int, per_page: int, days: int}
@returns(200)
@errors {401}

@endpoint GET /api/concepts/{id}
@required {id: int}
@optional {days: int}
@returns(200) {id: int(int64), name: str, slug: str, description: str?, parent_id: int(int64)?, score: num(float), similarity_threshold: num(float)?, created_at: str(date-time), updated_at: str(date-time), daily_metrics: [map], top_articles: [map]}
@errors {401}

@endpoint PATCH /api/concepts/{id}
@required {id: int}
@optional {concept: map{score: num, description: str, similarity_threshold: num}}
@returns(200) {id: int(int64), name: str, slug: str, description: str?, parent_id: int(int64)?, score: num(float), similarity_threshold: num(float)?, created_at: str(date-time), updated_at: str(date-time), daily_metrics: [map], top_articles: [map]}

@endpoint GET /api/concepts/{id}/articles
@required {id: int}
@optional {sort: str, page: int, per_page: int}
@returns(200)

@endpoint GET /api/admin/concepts
@optional {page: int, per_page: int}
@returns(200)

@endpoint POST /api/admin/concepts
@optional {concept: map{name!: str, description: str, parent_id: int, similarity_threshold: num, score: num}}
@returns(201) {id: int(int64), name: str, slug: str, description: str?, parent_id: int(int64)?, score: num(float), similarity_threshold: num(float)?, created_at: str(date-time), updated_at: str(date-time), daily_metrics: [map], top_articles: [map]}

@endpoint GET /api/admin/concepts/{id}
@required {id: int}
@returns(200) {id: int(int64), name: str, slug: str, description: str?, parent_id: int(int64)?, score: num(float), similarity_threshold: num(float)?, created_at: str(date-time), updated_at: str(date-time), daily_metrics: [map], top_articles: [map]}

@endpoint PATCH /api/admin/concepts/{id}
@required {id: int}
@optional {concept: map{name: str, description: str, parent_id: int, similarity_threshold: num, score: num}}
@returns(200) {id: int(int64), name: str, slug: str, description: str?, parent_id: int(int64)?, score: num(float), similarity_threshold: num(float)?, created_at: str(date-time), updated_at: str(date-time), daily_metrics: [map], top_articles: [map]}

@endpoint DELETE /api/admin/concepts/{id}
@required {id: int}
@returns(204)

@endpoint POST /api/admin/concepts/{id}/trigger_lookback
@required {id: int, days: int}
@returns(200)

@endpoint GET /api/concepts/search
@required {q: str}
@optional {per_page: int, threshold: num}
@returns(200)
@errors {400, 401}

@endpoint PATCH /api/feedback_messages/{id}
@required {id: int}
@optional {feedback_message: map{status!: str}}
@returns(200)

@endpoint GET /api/follows/tags
@returns(200)
@errors {401}

@endpoint GET /api/followers/users
@optional {page: int(int32)=1, per_page: int(int32)=30, sort: str}
@returns(200)
@errors {401}

@endpoint POST /api/follows
@optional {user_ids: [int], organization_ids: [int]}
@returns(200) {outcome: str}

@endpoint GET /api/health_checks/app
@optional {health-check-token: str}
@returns(200)

@endpoint GET /api/health_checks/database
@optional {health-check-token: str}
@returns(200)

@endpoint GET /api/health_checks/cache
@optional {health-check-token: str}
@returns(200)

@endpoint GET /api/instance
@returns(200)

@endpoint GET /api/organizations/{username}
@required {username: str}
@returns(200)
@errors {404}

@endpoint GET /api/organizations/{organization_id_or_username}/users
@required {organization_id_or_username: str}
@optional {page: int(int32)=1, per_page: int(int32)=30}
@returns(200)
@errors {404}

@endpoint GET /api/organizations/{organization_id_or_username}/articles
@required {organization_id_or_username: str}
@optional {page: int(int32)=1, per_page: int(int32)=30}
@returns(200)
@errors {404}

@endpoint GET /api/organizations
@optional {page: int(int32)=1, per_page: int(int32)=10}
@returns(200)

@endpoint POST /api/organizations
@optional {type_of: str, username: str, name: str, summary: str, twitter_username: str, github_username: str, url: str, location: str, joined_at: str, tech_stack: str, tag_line: str, story: str}
@returns(201)
@errors {401, 422}

@endpoint GET /api/organizations/{id}
@required {id: int}
@returns(200)
@errors {404}

@endpoint PUT /api/organizations/{id}
@required {id: int(int32)}
@optional {type_of: str, username: str, name: str, summary: str, twitter_username: str, github_username: str, url: str, location: str, joined_at: str, tech_stack: str, tag_line: str, story: str}
@returns(200)
@errors {401, 404, 422}

@endpoint DELETE /api/organizations/{id}
@required {id: int(int32)}
@returns(200)
@errors {401}

@endpoint GET /api/pages
@returns(200)

@endpoint POST /api/pages
@optional {title: str, slug: str, description: str, body_markdown: str, body_json: str, is_top_level_path: bool, template: str(contained/full_within_layout/nav_bar_included/json/css/txt)=contained}
@returns(200)
@errors {401, 422}

@endpoint GET /api/pages/{id}
@required {id: int(int32)}
@returns(200) {title: str, slug: str, description: str, body_markdown: str?, body_json: str?, is_top_level_path: bool, social_image: map?, template: str}

@endpoint PUT /api/pages/{id}
@required {id: int(int32), title: str, slug: str, description: str, template: str(contained/full_within_layout/nav_bar_included/json/css/txt)=contained}
@optional {body_markdown: str, body_json: str, is_top_level_path: bool, social_image: map}
@returns(200) {title: str, slug: str, description: str, body_markdown: str?, body_json: str?, is_top_level_path: bool, social_image: map?, template: str}
@errors {401, 422}

@endpoint DELETE /api/pages/{id}
@required {id: int(int32)}
@returns(200) {title: str, slug: str, description: str, body_markdown: str?, body_json: str?, is_top_level_path: bool, social_image: map?, template: str}
@errors {401, 422}

@endpoint GET /api/podcast_episodes
@optional {page: int(int32)=1, per_page: int(int32)=30, username: str}
@returns(200)
@errors {404}

@endpoint GET /api/profile_images/{username}
@required {username: str}
@returns(200)
@errors {404}

@endpoint POST /api/reactions/toggle
@required {category: str(like/unicorn/exploding_head/raised_hands/fire), reactable_id: int(int32), reactable_type: str(Comment/Article/User)}
@returns(200)
@errors {401}

@endpoint POST /api/reactions
@required {category: str(like/unicorn/exploding_head/raised_hands/fire), reactable_id: int(int32), reactable_type: str(Comment/Article/User)}
@returns(200)
@errors {401}

@endpoint GET /api/readinglist
@optional {page: int(int32)=1, per_page: int(int32)=30}
@returns(200)
@errors {401}

@endpoint GET /api/recommended_articles_lists
@optional {page: int, search: str}
@returns(200)

@endpoint POST /api/recommended_articles_lists
@required {placement_area: str, user_id: int}
@optional {name: str, expires_at: str(date-time), article_ids: [int]}
@returns(201) {id: int(int64), name: str, placement_area: str, expires_at: str(date-time)?, user_id: int(int64), article_ids: [int], created_at: str(date-time), updated_at: str(date-time)}

@endpoint GET /api/recommended_articles_lists/{id}
@required {id: int}
@returns(200) {id: int(int64), name: str, placement_area: str, expires_at: str(date-time)?, user_id: int(int64), article_ids: [int], created_at: str(date-time), updated_at: str(date-time)}

@endpoint PATCH /api/recommended_articles_lists/{id}
@required {id: int}
@optional {name: str, placement_area: str, expires_at: str(date-time), user_id: int, article_ids: [int]}
@returns(200) {id: int(int64), name: str, placement_area: str, expires_at: str(date-time)?, user_id: int(int64), article_ids: [int], created_at: str(date-time), updated_at: str(date-time)}

@endpoint GET /api/admin/request_redirects
@optional {page: int, per_page: int}
@returns(200)

@endpoint POST /api/admin/request_redirects
@optional {request_redirect: map{original_url!: str, destination_url!: str, request_domain!: str}}
@returns(201) {id: int(int64), original_url: str, destination_url: str, request_domain: str, created_at: str(date-time), updated_at: str(date-time)}

@endpoint GET /api/admin/request_redirects/{id}
@required {id: int}
@returns(200) {id: int(int64), original_url: str, destination_url: str, request_domain: str, created_at: str(date-time), updated_at: str(date-time)}

@endpoint PATCH /api/admin/request_redirects/{id}
@required {id: int}
@optional {request_redirect: map{original_url: str, destination_url: str, request_domain: str}}
@returns(200) {id: int(int64), original_url: str, destination_url: str, request_domain: str, created_at: str(date-time), updated_at: str(date-time)}

@endpoint DELETE /api/admin/request_redirects/{id}
@required {id: int}
@returns(204)

@endpoint GET /api/subforems
@returns(200)

@endpoint GET /api/surveys
@optional {page: int(int32)=1, per_page: int(int32)=30, active: bool}
@returns(200)
@errors {401}

@endpoint POST /api/surveys
@required {survey: map{title: str, survey_type_of: str, type_of: str, active: bool, display_title: bool, allow_resubmission: bool, daily_email_distributions: int(int32), extra_email_context_paragraph: str, target_response_count: int(int32), target_completion_date: str(date-time), polls: [map]}}
@returns(201)
@errors {401, 422}

@endpoint GET /api/surveys/{id_or_slug}
@required {id_or_slug: str}
@returns(200)
@errors {401, 404}

@endpoint PATCH /api/surveys/{id_or_slug}
@required {id_or_slug: str, survey: map{title: str, survey_type_of: str, type_of: str, active: bool, display_title: bool, allow_resubmission: bool, daily_email_distributions: int(int32), extra_email_context_paragraph: str, target_response_count: int(int32), target_completion_date: str(date-time), polls: [map]}}
@returns(200)
@errors {401, 404, 422}

@endpoint DELETE /api/surveys/{id_or_slug}
@required {id_or_slug: str}
@returns(204)
@errors {401, 404, 422}

@endpoint GET /api/surveys/{id_or_slug}/poll_votes
@required {id_or_slug: str}
@optional {per_page: int(int32)=30, after: int}
@returns(200)
@errors {401, 404}

@endpoint GET /api/surveys/{id_or_slug}/poll_text_responses
@required {id_or_slug: str}
@optional {per_page: int(int32)=30, after: int}
@returns(200)
@errors {401, 404}

@endpoint GET /api/tags
@optional {page: int(int32)=1, per_page: int(int32)=10}
@returns(200)

@endpoint GET /api/trends
@optional {page: int(int32)=1, per_page: int(int32)=10}
@returns(200)

@endpoint GET /api/trends/{id_or_slug}
@required {id_or_slug: str}
@returns(200)
@errors {404}

@endpoint GET /api/trends/{trend_id_or_slug}/articles
@required {trend_id_or_slug: str}
@optional {page: int(int32)=1, per_page: int(int32)=10}
@returns(200)
@errors {404}

@endpoint PUT /api/users/{id}/suspend
@required {id: int(int32)}
@returns(204)
@errors {401, 404}

@endpoint PUT /api/users/{id}/limited
@required {id: int(int32)}
@returns(204)
@errors {401, 404}

@endpoint DELETE /api/users/{id}/limited
@required {id: int(int32)}
@returns(204)
@errors {401, 404}

@endpoint PUT /api/users/{id}/spam
@required {id: int(int32)}
@returns(204)
@errors {401, 404}

@endpoint DELETE /api/users/{id}/spam
@required {id: int(int32)}
@returns(204)
@errors {401, 404}

@endpoint PUT /api/users/{id}/trusted
@required {id: int(int32)}
@returns(204)
@errors {401, 404}

@endpoint DELETE /api/users/{id}/trusted
@required {id: int(int32)}
@returns(204)
@errors {401, 404}

@endpoint GET /api/users/me
@returns(200)
@errors {401}

@endpoint GET /api/users/{id}
@required {id: str}
@returns(200)

@endpoint GET /api/users/search
@required {email: str}
@returns(200) {type_of: str, id: int(int64), username: str, name: str, summary: str?, twitter_username: str, github_username: str, email: str?, website_url: str?, location: str?, joined_at: str, profile_image: str, badge_ids: [int]}

@endpoint PUT /api/users/{id}/unpublish
@required {id: int(int32)}
@returns(204)

@endpoint POST /api/admin/users
@optional {email: str, name: str}
@returns(200)

@endpoint GET /api/admin/users
@optional {page: int, per_page: int, email: str, username: str}
@returns(200)

@endpoint GET /api/admin/users/{id}
@required {id: int}
@returns(200)

@endpoint PATCH /api/admin/users/{id}
@required {id: int}
@optional {name: str, username: str, summary: str, location: str, website_url: str}
@returns(200)

@endpoint PUT /api/admin/users/{id}/email
@required {id: int, email: str}
@returns(200)

@endpoint PUT /api/admin/users/{id}/status
@required {id: int, status: str}
@optional {note: str}
@returns(200)

@endpoint PUT /api/admin/users/{id}/notification_settings
@required {id: int, notification_setting: map{email_newsletter: bool, email_digest_periodic: bool, email_comment_notifications: bool, email_follower_notifications: bool, email_mention_notifications: bool, email_unread_notifications: bool, email_badge_notifications: bool}}
@returns(200)

@endpoint POST /api/admin/users/{id}/merge
@required {id: int, merge_user_id: int}
@returns(200)

@endpoint GET /api/admin/users/{user_id}/notes
@required {user_id: int}
@returns(200)

@endpoint POST /api/admin/users/{user_id}/notes
@required {user_id: int, content: str}
@optional {reason: str}
@returns(201)

@endpoint GET /api/admin/users/{user_id}/identities
@required {user_id: int}
@returns(200)

@endpoint POST /api/admin/users/{user_id}/identities
@required {user_id: int, provider: str, uid: str}
@optional {username: str}
@returns(201)

@endpoint DELETE /api/admin/users/{user_id}/identities/{id}
@required {user_id: int, id: int}
@returns(204)

@endpoint POST /api/admin/users/identities/bulk
@required {provider: str, identities: [map{user_id!: int, uid!: str}]}
@returns(200)

@endpoint GET /api/videos
@optional {page: int(int32)=1, per_page: int(int32)=24}
@returns(200)

@end
