> ## Documentation Index
> Fetch the complete documentation index at: https://docs.openpulse.cloud/llms.txt
> Use this file to discover all available pages before exploring further.

# Derive a listener plan from a website

> Crawls a website and returns a complete listener plan: keywords, exclusions, per-source queries, and a mode-specific profile — target roles, rival names and pain vocabulary, or place categories. Everything is editable before it runs.

This is the step every competing product replaces with an empty keyword box.

Rate limited to 5 calls an hour per key, and counted against the workspace's monthly allowance. An unreadable website answers 422 with `website_unreadable` rather than an error: the caller is asked to describe the business instead. Nothing is counted against the allowance unless the analysis succeeded.

To recover from a `website_unreadable` 422, resend the same request with `description` filled in and `acceptLimited: true`: the plan is then generated from the description instead of a crawl.



## OpenAPI

````yaml /api-reference/openapi.json post /v1/listeners/analyse-website
openapi: 3.1.0
info:
  title: Openpulse API
  version: 1.0.0
  summary: Read buying signals, accounts and pipeline from an Openpulse workspace.
  description: >-
    The programmatic surface of Openpulse, authenticated with a workspace API
    key.


    **A key always acts as a member, never an admin.** Privilege comes from
    scopes, and

    the scope vocabulary has no way to express an admin action: a leaked key can
    read

    signals and move deals, and can never create a listener, change billing, or
    mint

    another key. Routes that declare no scope are unreachable by a key at all,
    which is

    why this document describes fewer operations than the server has handlers.


    For an assistant rather than a program, prefer the MCP server at `/mcp`: it
    is the

    same data with tool descriptions a model can act on, and it uses OAuth
    rather than a

    shared secret. See `/api.md`.


    **Webhooks**, described below, are the other direction: calls this
    workspace's server

    makes to yours. Verify each one by recomputing the HMAC and comparing it to
    the

    `x-openpulse-signature` header — an unverified delivery is a POST from
    anyone

    who finds the URL, not proof it came from Openpulse.
  contact:
    email: support@openpulse.cloud
    url: https://openpulse.cloud/api.md
  license:
    name: Proprietary
    identifier: LicenseRef-Openpulse-Terms
servers:
  - url: https://api.openpulse.cloud
    description: Production
security:
  - apiKey: []
externalDocs:
  url: https://openpulse.cloud/api.md
  description: Openpulse for agents
paths:
  /v1/listeners/analyse-website:
    post:
      tags:
        - listeners
      summary: Derive a listener plan from a website
      description: >-
        Crawls a website and returns a complete listener plan: keywords,
        exclusions, per-source queries, and a mode-specific profile — target
        roles, rival names and pain vocabulary, or place categories. Everything
        is editable before it runs.


        This is the step every competing product replaces with an empty keyword
        box.


        Rate limited to 5 calls an hour per key, and counted against the
        workspace's monthly allowance. An unreadable website answers 422 with
        `website_unreadable` rather than an error: the caller is asked to
        describe the business instead. Nothing is counted against the allowance
        unless the analysis succeeded.


        To recover from a `website_unreadable` 422, resend the same request with
        `description` filled in and `acceptLimited: true`: the plan is then
        generated from the description instead of a crawl.
      operationId: postV1ListenersAnalyseWebsite
      parameters: []
      requestBody:
        required: true
        description: >-
          The wizard's step-1 answers. `sources` and `objective` decide the
          mode: `find_local_prospects` plans a `places` listener,
          `find_dissatisfaction` plans a `rivals` listener, and every other
          objective plans `market`.
        content:
          application/json:
            schema:
              type: object
              required:
                - companyName
                - website
                - objective
                - sources
              properties:
                companyName:
                  type: string
                  minLength: 2
                  maxLength: 120
                  description: The company's name.
                website:
                  type: string
                  format: uri
                  description: The company's homepage. This is what gets crawled.
                objective:
                  type: string
                  enum:
                    - find_customers
                    - find_competitors
                    - find_partnerships
                    - find_dissatisfaction
                    - find_local_prospects
                    - custom
                  description: >-
                    What the listener is for. `find_dissatisfaction` returns a
                    rival profile (competitor names, pain vocabulary);
                    `find_local_prospects` returns a places profile (categories,
                    locations); every other value, including `custom`, returns a
                    market keyword plan.
                customObjective:
                  type: string
                  maxLength: 500
                  description: >-
                    Free text describing the objective when `objective` is
                    `custom`.
                sources:
                  type: array
                  minItems: 1
                  items:
                    type: string
                    enum:
                      - x
                      - linkedin
                      - facebook
                      - reddit
                      - hackernews
                      - linkedin_jobs
                      - indeed
                      - ashby
                      - greenhouse
                      - lever
                      - google_reviews
                      - g2
                      - trustpilot
                      - youtube
                  description: Which platforms the resulting plan should carry queries for.
                description:
                  type: string
                  maxLength: 2000
                  description: >-
                    A short description of the business, used to plan when the
                    site cannot be crawled. Required on the retry that follows a
                    `website_unreadable` 422.
                acceptLimited:
                  type: boolean
                  default: false
                  description: >-
                    Set on a retry to proceed without a successful crawl,
                    planning from `description` instead. Sending it `true` on
                    the first call skips the crawl for no reason, since there is
                    no `description` yet to plan from.
      responses:
        '200':
          description: Success.
          content:
            application/json:
              schema:
                type: object
        '401':
          description: The key is missing, malformed, expired or revoked.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: >-
            The key lacks the `listeners:analyse` scope, or the workspace's plan
            does not include this.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '429':
          description: Rate limited. Retry after the interval in the response headers.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
      security:
        - apiKey:
            - listeners:analyse
components:
  schemas:
    Error:
      type: object
      properties:
        message:
          type: string
          description: Human-readable explanation.
        code:
          type: string
          description: Stable machine-readable error code.
      required:
        - message
  securitySchemes:
    apiKey:
      type: http
      scheme: bearer
      description: >-
        A workspace API key, created in workspace settings and sent as
        `Authorization: Bearer op_live_…`. Session cookies are not accepted.
        Scopes: signals:read, accounts:read, listeners:read, listeners:analyse,
        pipeline:read, pipeline:write, collections:write, export:read.

````

## Related topics

- [Listeners: what you are watching for](/guides/listeners.md)
- [Setting up](/guides/setting-up.md)
- [Tools](/mcp/tools.md)
