openapi: 3.2.0 info: title: Forem API V1 Surveys API version: 1.0.0 description: Access Forem articles, users and other resources via API. servers: - url: https://dev.to description: Production server security: - api-key: [] - bearer_auth: [] tags: - name: Surveys paths: /api/surveys: get: summary: List surveys tags: - Surveys description: 'Retrieve a list of surveys configured on the platform. ### Surveys Overview: - Surveys are admin-defined questionnaires consisting of multiple choice or text polls. - Requires Administrator authorization. - 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. operationId: postApiSurveys x-operation-id-source: derived /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. ### Nested Format Details: - Returns the target Survey object including all associated Polls, multiple choice options, and configuration states. - 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. operationId: patchApiSurveysByIdOrSlug x-operation-id-source: derived 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 operationId: deleteApiSurveysByIdOrSlug x-operation-id-source: derived /api/surveys/{id_or_slug}/poll_votes: get: summary: Survey poll votes tags: - Surveys description: 'Retrieve multiple-choice poll votes for a specific survey. ### Cursor Pagination Tip: - Uses cursor-based pagination to safely stream high volumes of voting records. - Specify the `after` query parameter with the last retrieved record ID to get the next page. - 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. ### Integration & Cursor Tip: - Fetches written user answers for text-input questions. - Uses cursor-based pagination (`after` query param) to stream responses. - 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 components: parameters: 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 pageParam: in: query name: page required: false description: Pagination page schema: type: integer format: int32 minimum: 1 default: 1 schemas: 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 - 'null' description: Option text in markdown processed_html: type: - string - 'null' 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 - 'null' description: Additional descriptive text for the option required: - type_of - id - markdown - processed_html - position - poll_votes_count 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 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 - 'null' description: Question text in markdown prompt_html: type: - string - 'null' 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 - 'null' format: int32 description: Minimum value for scale polls scale_max: type: - integer - 'null' format: int32 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 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 - beta_testing description: Survey category type_of: type: string enum: - community_pulse - industry - fun - beta_testing 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 - 'null' description: Optional context paragraph for emails target_response_count: type: integer format: int32 description: Target response count target_completion_date: type: - string - 'null' format: date-time 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 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 - beta_testing description: Survey category active: type: - boolean - 'null' 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 - 'null' description: Optional context paragraph for emails target_response_count: type: integer format: int32 description: Target response count target_completion_date: type: - string - 'null' format: date-time 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 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 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)" bearer_auth: type: http scheme: bearer bearerFormat: JWT description: Short-lived RS256 RFC 9068 access token issued by the configured delegation service and verified against its configured JWKS. The issuer authorizes the client and requested operation before minting the token; Forem validates the token and resolves its subject and owner to a local user. An invalid token returns 401; an unavailable trust dependency with no usable cached key returns 503.