# PushFeedback REST API specification.
#
# Provenance: generated from the pushfeedback-django repo, branch
# feat/feedback-read-api at commit e2f36ce, with drf-spectacular
# (python manage.py spectacular --file openapi.json --format openapi-json --validate),
# then curated by hand for the public docs: internal dashboard endpoints are
# removed, descriptions and examples are expanded. When the API changes,
# regenerate the raw schema from pushfeedback-django, diff it against this
# file, and fold in the changes. Then run:
#   npm run sync-api-schema && npm run gen-api-docs
openapi: 3.0.3
info:
  title: PushFeedback API
  version: "1.0"
  description: |
    The PushFeedback REST API lets you read the feedback collected for your
    projects and drive the same endpoints the feedback widget uses under the
    hood.

    Base URL: `https://app.pushfeedback.com`

    ## Authentication

    Endpoints that read or change your data require an API key sent in the
    `Authorization` header:

    ```
    Authorization: Api-Key <your-api-key>
    ```

    API keys are scoped to a team, and can optionally be restricted to a
    single project of that team. Every key holds one or more scopes:

    | Scope | Allows |
    |-------|--------|
    | `feedback:read` | List a project's feedback |
    | `feedback:submit` | Submit feedback to projects that require a submit key |
    | `project:manage` | Update project settings, such as the site URL |

    A key can hold any combination of scopes; pick them when creating the
    key in the dashboard.

    Requests for a project the key does not cover, either because the project
    belongs to another team or falls outside the key's project restriction,
    return `404 Not Found`, so a leaked key cannot be used to probe which
    project identifiers exist. Requests that the key's scopes do not allow on
    a project it does cover return `403 Forbidden` with a message naming the
    missing scope.

    ### Getting an API key

    Team admins can create and revoke API keys from the team settings page
    on [app.pushfeedback.com](https://app.pushfeedback.com). The full key is
    shown once, at creation time; store it securely. Official PushFeedback
    integrations may provision keys automatically.

    The endpoints the widget calls on page load and on submit, such as
    retrieving the project configuration and submitting feedback, are public
    and need no API key by default. Projects can opt in to requiring a key
    for feedback submission; see the submit endpoint.

    ## Versioning

    The API is versioned. All endpoints live under `/api/v1/`.

    ## Rate limits

    Public endpoints are rate limited per client IP. If you exceed a limit,
    the API responds with `429 Too Many Requests`.
  contact:
    email: support@pushfeedback.com
servers:
  - url: https://app.pushfeedback.com
tags:
  - name: projects
    x-displayName: Projects
    description: Public project configuration, consumed by the feedback widget.
  - name: feedback
    x-displayName: Feedback
    description: Submit feedback and read the feedback collected for a project.
paths:
  /api/v1/projects/{slug}/:
    get:
      operationId: get_project_config
      summary: Get project configuration
      description: |
        Retrieve the public configuration of a project: whitelabel status,
        reCAPTCHA settings, plan, and plan capabilities. The feedback widget
        calls this endpoint on page load.

        Responses are cached server side for up to 60 seconds, so plan or
        whitelabel changes can take up to a minute to be reflected.

        The `recaptcha_site_key` is safe to expose here: reCAPTCHA site keys
        are public by design, and the secret key is never returned.
      tags:
        - projects
      security: []
      parameters:
        - in: path
          name: slug
          description: The project identifier, shown in the dashboard as the project ID.
          schema:
            type: string
          required: true
          example: wlkz1eomrs
      responses:
        "200":
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Project"
          description: The project's public configuration.
        "404":
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NotFound"
          description: No project exists with this identifier.
  /api/v1/feedback/:
    post:
      operationId: create_feedback
      summary: Submit feedback
      description: |
        Create a feedback entry for a project. This is the endpoint the
        feedback widget calls when a visitor submits the form; you can call it
        directly to submit feedback from your own UI.

        No API key is required by default, but the `url` field must belong to
        one of the domains configured for the project. Requests from other
        origins are rejected with `403 Forbidden`.

        Projects can enable the require a submit key option. Submissions to
        such a project must carry an API key with the `feedback:submit` scope
        covering the project: requests without a key get `401 Unauthorized`,
        and requests with a key that lacks the scope get `403 Forbidden`.
        This suits internal sites and backend integrations where everyone who
        can see the page may know the key, but outsiders cannot submit. The
        project configuration's `require_submit_key` field tells you whether
        a project has this enabled.

        If the project has reCAPTCHA enabled, a valid `recaptchaToken` is
        required.

        A `201` response means the feedback was stored. A `202` response means
        the feedback was stored, but the project's team has exceeded the free
        plan limit; upgrade to keep collecting feedback reliably.
      tags:
        - feedback
      security:
        - {}
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateFeedback"
      responses:
        "201":
          content:
            application/json:
              schema:
                type: string
                description: A short reference to the created feedback entry.
          description: Feedback stored.
        "202":
          content:
            application/json:
              schema:
                type: string
                description: A short reference to the created feedback entry.
          description: Feedback stored, but the team is over the free plan limit.
        "400":
          content:
            text/html:
              schema:
                type: string
          description: Missing or invalid field, invalid screenshot payload, or failed reCAPTCHA verification.
        "401":
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AuthenticationError"
          description: The project requires a submit key and no API key was provided.
        "403":
          content:
            application/json:
              schema:
                type: string
          description: The `url` does not match any of the project's configured domains, or the provided API key lacks the `feedback:submit` scope.
        "404":
          content:
            text/html:
              schema:
                type: string
          description: No project exists with this identifier.
        "429":
          description: Rate limit exceeded.
  /api/v1/projects/{slug}/feedback/:
    get:
      operationId: list_project_feedback
      summary: List project feedback
      description: |
        List a project's feedback entries, newest first, 25 per page. Spam
        entries are excluded.

        Requires an API key with the `feedback:read` scope in the
        `Authorization` header. The key must cover the project in the URL; a
        key for another team's project, or one outside the key's project
        restriction, returns `404 Not Found`.

        Each entry with a screenshot includes a `screenshot_url` that is
        pre-signed and valid for five minutes. Fetch screenshots right after
        listing, or request the page again for fresh URLs.
      tags:
        - feedback
      security:
        - ApiKeyAuth: []
      parameters:
        - in: path
          name: slug
          description: The project identifier the API key belongs to.
          schema:
            type: string
          required: true
          example: wlkz1eomrs
        - in: query
          name: page
          description: A page number within the paginated result set.
          required: false
          schema:
            type: integer
          example: 1
      responses:
        "200":
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PaginatedFeedbackList"
          description: One page of feedback entries.
        "401":
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AuthenticationError"
          description: Missing or invalid API key.
        "404":
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NotFound"
          description: The API key does not cover this project, or the project does not exist.
        "429":
          description: Rate limit exceeded.
  /api/v1/projects/{slug}/site-url/:
    patch:
      operationId: update_project_site_url
      summary: Update project site URL
      description: |
        Update the project's primary site URL. Feedback submissions are
        validated against this URL's hostname, so keep it in sync when your
        site changes domain.

        Requires an API key with the `project:manage` scope in the
        `Authorization` header. The key must cover the project in the URL; a
        key for another team's project, or one outside the key's project
        restriction, returns `404 Not Found`. A key without the
        `project:manage` scope returns `403 Forbidden`.

        The URL is normalized before saving: a trailing slash is stripped. The
        response contains the normalized URL.
      tags:
        - projects
      security:
        - ApiKeyAuth: []
      parameters:
        - in: path
          name: slug
          description: The project identifier the API key belongs to.
          schema:
            type: string
          required: true
          example: wlkz1eomrs
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                url:
                  type: string
                  format: uri
                  description: The new primary site URL. Must be an absolute http(s) URL.
              required:
                - url
            example:
              url: https://docs.example.com
      responses:
        "200":
          content:
            application/json:
              schema:
                type: object
                properties:
                  url:
                    type: string
                    format: uri
                    description: The normalized site URL that was saved.
                  project:
                    type: string
                    description: The project identifier.
                required:
                  - url
                  - project
              example:
                url: https://docs.example.com
                project: wlkz1eomrs
          description: The site URL was updated.
        "400":
          content:
            application/json:
              schema:
                type: object
                properties:
                  error:
                    type: string
              example:
                error: url must be an absolute http(s) URL.
          description: The URL is missing or not an absolute http(s) URL.
        "401":
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AuthenticationError"
          description: Missing or invalid API key.
        "403":
          content:
            application/json:
              schema:
                type: object
                properties:
                  detail:
                    type: string
              example:
                detail: This API key does not have the project:manage scope.
          description: The API key covers this project but lacks the `project:manage` scope.
        "404":
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NotFound"
          description: The API key does not cover this project, or the project does not exist.
        "429":
          description: Rate limit exceeded.
components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: Authorization
      description: |
        Team API key, sent as `Authorization: Api-Key <your-api-key>`.
        Note the `Api-Key` prefix before the key itself. Keys hold one or
        more scopes (`feedback:read`, `feedback:submit`, `project:manage`)
        and may be restricted to one project.
  schemas:
    Project:
      type: object
      description: Public configuration of a project.
      properties:
        whitelabel:
          type: boolean
          readOnly: true
          description: Whether the project hides PushFeedback branding in the widget.
        recaptcha_enabled:
          type: boolean
          readOnly: true
          description: Whether feedback submissions require a reCAPTCHA v3 token.
        recaptcha_site_key:
          type: string
          readOnly: true
          nullable: true
          description: The reCAPTCHA v3 site key to use when `recaptcha_enabled` is true.
        require_submit_key:
          type: boolean
          readOnly: true
          description: |
            Whether feedback submissions to this project require an API key
            with the `feedback:submit` scope.
        plan:
          type: string
          readOnly: true
          description: The plan of the team that owns the project, for example `free`, `professional`, or `business`.
        features:
          type: array
          readOnly: true
          items:
            type: string
            enum:
              - whitelabel
              - ai_reports
          description: Plan capabilities enabled for the project.
      required:
        - whitelabel
        - recaptcha_enabled
        - recaptcha_site_key
        - plan
        - features
      example:
        whitelabel: false
        recaptcha_enabled: false
        recaptcha_site_key: null
        require_submit_key: false
        plan: professional
        features: []
    CreateFeedback:
      type: object
      description: A feedback submission, as sent by the feedback widget.
      properties:
        project:
          type: string
          description: The project identifier to attach the feedback to.
        url:
          type: string
          description: |
            The absolute URL of the page the feedback is about. Its hostname
            must match one of the project's configured domains. Truncated to
            200 characters on storage.
        message:
          type: string
          description: The feedback text. May be an empty string.
          default: ""
        rating:
          type: integer
          description: |
            The rating value. For `stars` mode, 1 to 5. For `thumbs` mode, 1
            for thumbs down and 5 for thumbs up. Use 0 when no rating was
            given.
          default: 0
        ratingMode:
          type: string
          enum:
            - stars
            - thumbs
          description: How the rating was collected.
          default: stars
        email:
          type: string
          description: The email address of the person leaving feedback, if collected.
        screenshot:
          type: string
          description: |
            An optional screenshot as a base64 data URL, for example
            `data:image/png;base64,...`.
        metadata:
          type: string
          description: Optional free-form metadata attached by the integration.
        session:
          type: string
          description: Optional session identifier used to group feedback from one visitor.
        recaptchaToken:
          type: string
          description: A reCAPTCHA v3 token. Required when the project has reCAPTCHA enabled.
      required:
        - project
        - url
      example:
        project: wlkz1eomrs
        url: https://docs.example.com/getting-started
        message: The install steps are missing the npm command.
        rating: 4
        ratingMode: stars
    Feedback:
      type: object
      description: Read-only shape of a feedback entry.
      properties:
        id:
          type: integer
          readOnly: true
          description: The numeric identifier of the feedback entry.
        message:
          type: string
          description: The feedback text.
        rating:
          type: integer
          readOnly: true
          description: |
            The normalized rating. For `stars` mode, 1 to 5. For `thumbs`
            mode, 1 for thumbs down and 5 for thumbs up. 0 means no rating.
        rating_mode:
          type: string
          maxLength: 20
          description: How the rating was collected, `stars` or `thumbs`.
        url:
          type: string
          maxLength: 200
          description: The URL of the page the feedback was left on.
        email:
          type: string
          nullable: true
          maxLength: 200
          description: The email address of the person who left the feedback, if collected.
        status:
          $ref: "#/components/schemas/FeedbackStatus"
        has_screenshot:
          type: boolean
          description: Whether the entry has a screenshot attached.
        screenshot_url:
          type: string
          readOnly: true
          nullable: true
          description: |
            A pre-signed URL for the screenshot, valid for five minutes.
            `null` when the entry has no screenshot.
        created_at:
          type: string
          format: date-time
          readOnly: true
          description: When the feedback was submitted.
      required:
        - id
        - message
        - rating
        - url
        - screenshot_url
        - created_at
      example:
        id: 4211
        message: The install steps are missing the npm command.
        rating: 4
        rating_mode: stars
        url: https://docs.example.com/getting-started
        email: reader@example.com
        status: new
        has_screenshot: false
        screenshot_url: null
        created_at: "2026-07-01T09:30:00Z"
    FeedbackStatus:
      type: string
      enum:
        - new
        - archived
      description: Triage status of the feedback entry.
    PaginatedFeedbackList:
      type: object
      properties:
        count:
          type: integer
          description: Total number of feedback entries for the project.
          example: 123
        next:
          type: string
          nullable: true
          format: uri
          description: URL of the next page, or `null` on the last page.
          example: https://app.pushfeedback.com/api/v1/projects/wlkz1eomrs/feedback/?page=3
        previous:
          type: string
          nullable: true
          format: uri
          description: URL of the previous page, or `null` on the first page.
          example: https://app.pushfeedback.com/api/v1/projects/wlkz1eomrs/feedback/?page=1
        results:
          type: array
          items:
            $ref: "#/components/schemas/Feedback"
      required:
        - count
        - results
    NotFound:
      type: object
      properties:
        detail:
          type: string
      example:
        detail: Not found.
    AuthenticationError:
      type: object
      properties:
        detail:
          type: string
      example:
        detail: Authentication credentials were not provided.
