unplain-io Feature requests API

The public, anonymous feature-request leaderboard.

Operations 5

GET /api/features List feature requests #
POST /api/features Submit a feature request #
GET /api/features/similar Find existing requests that look like the same idea #
POST /api/features/{id}/vote Add the caller's vote #
DELETE /api/features/{id}/vote Remove the caller's vote #

Work with this as data

Every API here is available over the APIs.io API and to AI agents over MCP.

MCP server

One button, every client — Claude, Cursor, VS Code and the rest.

https://apis.io/mcp

Tools for apis

7 MCP tools reach this
  • find_apisBrowse and filter every API in the catalog.
  • get_api_artifactsOne API's artifacts, grouped by type.
  • get_openapiThe primary OpenAPI for this API.
  • find_similar_apisAPIs that look like this one.
  • apis_io_searchSTART HERE — APIs, providers and tags for one query, each with its total.
  • resolveTurn a domain, URL or GitHub org into the provider it belongs to.
  • find_cohortsEvery scored population of providers in the catalog.
All 92 tools →

Call it yourself

curl for this page
This API
curl "https://apis.io/api/v1/apis/unplain-io:unplain-io-feature-requests-api"
All apis
curl "https://apis.io/api/v1/apis?limit=25"

Discovery needs no key. Ratings and market analysis are Pro.

Get an API key

Free tier, no form to fill in. Signing in shares your email address with us — we store it to create your key and to recognise you if you sign in with another provider. See our Privacy Policy and Terms.

A second provider on the same verified email joins the account you already have.

OpenAPI Specification

unplain-io-feature-requests-api-openapi.yml Raw ↑
openapi: 3.2.0
info:
  title: Unplain Io Feature requests API
  version: 1.0.0
  contact:
    name: Unplain
    email: info@unplain.io
    url: https://unplain.io/developers
  termsOfService: https://unplain.io/terms
  description: 'Operations tagged Feature requests across 2 of this provider''s published API definitions: app-unplain-io-openapi.json, unplain-io-openapi.json. Each path carries the servers of the definition it was published in.'
servers:
- url: https://app.unplain.io
  description: Unplain application API
tags:
- name: Feature requests
  description: The public, anonymous feature-request leaderboard.
  externalDocs:
    description: The board
    url: https://unplain.io/feature-requests
paths:
  /api/features:
    summary: The public feature-request leaderboard
    get:
      tags:
      - Feature requests
      operationId: listFeatureRequests
      summary: List feature requests
      description: 'One page of the board. Requests are publicly unattributed: no username, email or other submitter detail is returned, because none is stored on the public row. `voted` reflects the calling browser''s own votes and is false for a caller with no voter cookie.'
      security: []
      parameters:
      - name: sort
        in: query
        required: false
        description: '`top` ranks active requests by votes, `new` by submission time, `shipped` lists delivered ones. Anything else is treated as `top`.'
        schema:
          type: string
          enum:
          - top
          - new
          - shipped
          default: top
      - name: page
        in: query
        required: false
        description: 1-based page number. Clamped to 1..10000 rather than rejected.
        schema:
          type: integer
          minimum: 1
          maximum: 10000
          default: 1
      responses:
        '200':
          description: One page of requests.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FeatureListResult'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          description: The list could not be read.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error: list_failed
        '503':
          $ref: '#/components/responses/Unavailable'
    post:
      tags:
      - Feature requests
      operationId: submitFeatureRequest
      summary: Submit a feature request
      description: 'Creates a request and counts the submitter as its first vote.

        Submitting text that already exists does not create a second card: the

        caller''s vote is added to the existing one and the reply carries

        `duplicate: true` with status 200 instead of 201.


        Browser callers only. The request must come from an allowlisted origin

        and carry `Content-Type: application/json`, which together make a

        cross-site form submission impossible.'
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
              - text
              properties:
                text:
                  type: string
                  minLength: 3
                  maxLength: 200
                  description: The request, as one line of plain text.
            example:
              text: Export to EPUB as well as PDF
      responses:
        '200':
          description: An identical request already existed. The caller's vote was added to it.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SubmitResult'
        '201':
          description: The request was created.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SubmitResult'
        '400':
          description: The body or the text was rejected.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
              example:
                error: invalid_text
                reason: too_long
                message: Keep it under 200 characters.
                maxChars: 200
        '403':
          description: The `Origin` header is not on the allowlist.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error: origin_not_allowed
        '415':
          description: '`Content-Type` was not `application/json`.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error: unsupported_media_type
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          description: The request could not be stored.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error: submit_failed
        '503':
          $ref: '#/components/responses/Unavailable'
    servers:
    - url: https://app.unplain.io
      description: Unplain application API
  /api/features/similar:
    get:
      tags:
      - Feature requests
      operationId: findSimilarFeatureRequests
      summary: Find existing requests that look like the same idea
      description: 'Advisory only, and intended to be called while someone is typing. It never blocks a submission, and it answers `{ items: [] }` rather than an error when the query is too short or the lookup fails.'
      security: []
      parameters:
      - name: q
        in: query
        required: false
        description: Draft text. Fewer than 3 characters returns an empty list.
        schema:
          type: string
          maxLength: 200
      responses:
        '200':
          description: Up to three candidate matches, possibly none.
          content:
            application/json:
              schema:
                type: object
                required:
                - items
                properties:
                  items:
                    type: array
                    maxItems: 3
                    items:
                      $ref: '#/components/schemas/FeatureRequest'
        '429':
          $ref: '#/components/responses/RateLimited'
        '503':
          $ref: '#/components/responses/Unavailable'
    servers:
    - url: https://app.unplain.io
      description: Unplain application API
  /api/features/{id}/vote:
    parameters:
    - name: id
      in: path
      required: true
      description: The request's UUID, from `GET /api/features`.
      schema:
        type: string
        format: uuid
    post:
      tags:
      - Feature requests
      operationId: castFeatureVote
      summary: Add the caller's vote
      description: Idempotent. Voting twice leaves exactly one vote, and the count in the reply is recomputed by the database rather than derived from anything the client sent.
      security: []
      responses:
        '200':
          description: The recomputed vote state.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/VoteResult'
        '400':
          description: '`id` is not a UUID.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error: invalid_id
        '403':
          description: The `Origin` header is not on the allowlist.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error: origin_not_allowed
        '404':
          description: No request with that id.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error: not_found
        '415':
          description: '`Content-Type` was not `application/json`.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error: unsupported_media_type
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          description: The vote could not be recorded.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error: vote_failed
        '503':
          $ref: '#/components/responses/Unavailable'
    delete:
      tags:
      - Feature requests
      operationId: removeFeatureVote
      summary: Remove the caller's vote
      description: Idempotent. Removing a vote that does not exist is a success.
      security: []
      responses:
        '200':
          description: The recomputed vote state.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/VoteResult'
        '400':
          description: '`id` is not a UUID.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error: invalid_id
        '403':
          description: The `Origin` header is not on the allowlist.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error: origin_not_allowed
        '404':
          description: No request with that id.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error: not_found
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          description: The vote could not be recorded.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
              example:
                error: vote_failed
        '503':
          $ref: '#/components/responses/Unavailable'
    servers:
    - url: https://app.unplain.io
      description: Unplain application API
components:
  schemas:
    SubmitResult:
      type: object
      required:
      - request
      - duplicate
      properties:
        request:
          $ref: '#/components/schemas/FeatureRequest'
        duplicate:
          type: boolean
          description: True when an identical request already existed and the caller's vote was added to it instead.
    Error:
      type: object
      required:
      - error
      description: Every non-MCP error reply has this shape. `error` is a stable machine-readable code; branch on it rather than on the HTTP status alone.
      properties:
        error:
          type: string
          description: Stable error code.
          examples:
          - not_found
          - invalid_id
          - invalid_body
          - invalid_text
          - origin_not_allowed
          - unsupported_media_type
          - rate_limited
          - features_unavailable
          - list_failed
          - submit_failed
          - vote_failed
        message:
          type: string
          description: Human-readable explanation, when one adds anything.
        documentation:
          type: string
          format: uri
          description: Where to read about this error.
    FeatureListResult:
      type: object
      required:
      - items
      - total
      - page
      - pageCount
      - pageSize
      - sort
      properties:
        items:
          type: array
          items:
            $ref: '#/components/schemas/FeatureRequest'
        total:
          type: integer
          minimum: 0
        page:
          type: integer
          minimum: 1
        pageCount:
          type: integer
          minimum: 0
        pageSize:
          type: integer
          const: 20
        sort:
          type: string
          enum:
          - top
          - new
          - shipped
    VoteResult:
      type: object
      required:
      - voteCount
      - voted
      - milestone
      properties:
        voteCount:
          type: integer
          minimum: 0
        voted:
          type: boolean
        milestone:
          type: boolean
          description: True once the request has reached 50 votes, the point at which it moves to the top of the build list. Derived from the count on every read, never stored.
    ValidationError:
      allOf:
      - $ref: '#/components/schemas/Error'
      - type: object
        properties:
          reason:
            type: string
            description: Which rule the text broke.
            examples:
            - too_short
            - too_long
            - empty
          maxChars:
            type: integer
            description: The hard character cap.
    FeatureRequest:
      type: object
      required:
      - id
      - text
      - status
      - voteCount
      - createdAt
      - voted
      properties:
        id:
          type: string
          format: uuid
        text:
          type: string
          maxLength: 200
        status:
          type: string
          enum:
          - requested
          - planned
          - building
          - shipped
          description: '`declined` requests exist but are never returned publicly, so they cannot be resubmitted as new ideas.'
        voteCount:
          type: integer
          minimum: 0
        createdAt:
          type: string
          format: date-time
        voted:
          type: boolean
          description: Whether the calling browser has voted for this request.
  responses:
    Unavailable:
      description: A dependency Unplain needs is not reachable. Retry shortly.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: features_unavailable
    RateLimited:
      description: Too many requests. `Retry-After` gives the number of seconds to wait.
      headers:
        Retry-After:
          description: Seconds to wait before retrying.
          schema:
            type: integer
            minimum: 1
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          example:
            error: rate_limited
  securitySchemes:
    ApiKey:
      type: http
      scheme: bearer
      bearerFormat: unp_<base64url secret>
      description: 'An Unplain API key, created under Account settings, API keys, and sent as `Authorization: Bearer unp_...`. The key carries exactly the capabilities of the account''s plan: there is no separate API tier. Scopes are granted per key and enforced per MCP tool: `brand_themes:read`, `brand_themes:write`, `projects:read`, `projects:write`, `exports:write`, `ai_cover:write`, `ai_image:write`.'
externalDocs:
  description: Unplain developer documentation
  url: https://unplain.io/developers
x-refined-from:
- app-unplain-io-openapi.json
- unplain-io-openapi.json