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

# Generate startup ideas

> Synchronous idea generation - the backing route for the generate_ideas MCP tool. Spends no scan quota or tokens. Free accounts get 5 generations per rolling 24 hours per account (GENERATION_LIMIT when exhausted). Paid-or-credited accounts skip that cap and get limit: null, under a daily fair-use ceiling of 20 generations per rolling 24 hours per account, 15 of them with the Pro model (a Pro run counts toward both), shared with the idea generator on preuve.ai and refused with DAILY_LIMIT_REACHED. The Pro model is the default on paid plans (pro: false opts out) and the response pro says whether the Pro tier was used (under load a Pro-tier run can be served by a faster fallback model); pro: true without a paid plan is refused with PRO_PAID_REQUIRED, and a pro that is not a boolean or null is 400 INVALID_INPUT. Founder-profile tailoring is automatic on paid plans with a saved profile (fit: false opts out); the response's fitReason and fitHint state why tailoring did or did not apply.




## OpenAPI

````yaml /api-reference/openapi.yaml post /api/agent/tools/idea-generator
openapi: 3.1.0
info:
  title: Preuve Agent API
  version: '2.0'
  summary: Startup idea validation for AI agents and scripts.
  description: >
    API for programmatic startup idea validation: a 0-100 viability score,
    competitors, market sizing and source-linked evidence from 60+ live sources.
    Every request carries a single x-preuve-key header holding the whole API key
    - no signing, no bearer token. See the Authentication guide.


    Every response is JSON, errors included: an unknown route answers 404 with
    the same { error, code } envelope as every other failure, so branch on code
    and never parse a message. Error codes are stable and retired ones stay
    listed on the Errors page. scanType "starter" costs nothing and returns a
    real report, which makes it the sandbox: an agent can exercise the whole
    create - poll - enrich - export loop without spending a cent. GET /api/agent
    is the unauthenticated discovery document.
  termsOfService: https://preuve.ai/terms
  contact:
    name: Preuve AI developer support
    url: https://preuve.ai/support
    email: hello@preuve.ai
servers:
  - url: https://preuve.ai
security:
  - preuveKey: []
tags:
  - name: Discovery
    description: Unauthenticated documents that describe the API itself.
  - name: Analyses
    description: One idea at a time - create, poll, enrich, export.
  - name: Batches
    description: Up to five analyses in one idempotent batch.
  - name: Tools
    description: Utilities that spend no scan quota.
  - name: Agency
    description: >-
      Client work on a Consultant or Agency workspace (agency:read /
      agency:write).
externalDocs:
  description: Developer docs - quickstart, authentication, quotas, error codes
  url: https://docs.preuve.ai/
paths:
  /api/agent/tools/idea-generator:
    post:
      tags:
        - Tools
      summary: Generate startup ideas
      description: >
        Synchronous idea generation - the backing route for the generate_ideas
        MCP tool. Spends no scan quota or tokens. Free accounts get 5
        generations per rolling 24 hours per account (GENERATION_LIMIT when
        exhausted). Paid-or-credited accounts skip that cap and get limit: null,
        under a daily fair-use ceiling of 20 generations per rolling 24 hours
        per account, 15 of them with the Pro model (a Pro run counts toward
        both), shared with the idea generator on preuve.ai and refused with
        DAILY_LIMIT_REACHED. The Pro model is the default on paid plans (pro:
        false opts out) and the response pro says whether the Pro tier was used
        (under load a Pro-tier run can be served by a faster fallback model);
        pro: true without a paid plan is refused with PRO_PAID_REQUIRED, and a
        pro that is not a boolean or null is 400 INVALID_INPUT. Founder-profile
        tailoring is automatic on paid plans with a saved profile (fit: false
        opts out); the response's fitReason and fitHint state why tailoring did
        or did not apply.
      operationId: generateIdeas
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - interests
              properties:
                interests:
                  type: string
                  minLength: 3
                  maxLength: 500
                  description: Interests or skills to generate ideas from.
                budget:
                  type: string
                  maxLength: 100
                audience:
                  type: string
                  maxLength: 100
                industry:
                  type: string
                  maxLength: 100
                language:
                  type: string
                  maxLength: 10
                pro:
                  type:
                    - boolean
                    - 'null'
                  description: >-
                    Use the Pro model. Defaults to true on paid plans and false
                    otherwise (null counts as omitted); true without a paid plan
                    is refused.
                fit:
                  type: boolean
                  description: >
                    Founder-profile tailoring is automatic on paid plans with a
                    saved profile; pass false to opt out. Passing true is never
                    needed.
      responses:
        '200':
          description: >
            Generated ideas. remaining and limit are null for paid-or-credited
            accounts, whose daily ceiling is not counted down here; free
            accounts see the rolling-24h window state. Pain points are teased as
            locked on non-paid plans.
          content:
            application/json:
              schema:
                type: object
                properties:
                  ideas:
                    type: array
                    maxItems: 5
                    items:
                      $ref: '#/components/schemas/GeneratedIdea'
                  remaining:
                    type:
                      - integer
                      - 'null'
                  limit:
                    type:
                      - integer
                      - 'null'
                  pro:
                    type: boolean
                  fit:
                    type: boolean
                  fitReason:
                    type: string
                    enum:
                      - applied
                      - disabled
                      - requires_paid_plan
                      - no_profile
                  fitHint:
                    type: string
                    description: >
                      Present when tailoring did not apply for a reason worth
                      relaying to the user (no_profile, requires_paid_plan).
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          description: >
            Pro model requested without a paid plan (PRO_PAID_REQUIRED, body
            carries an upsell object), or the key lacks analysis:write
            (INSUFFICIENT_SCOPE - see the InsufficientScope response).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
        '413':
          $ref: '#/components/responses/PayloadTooLarge'
        '429':
          description: >
            Free generation cap reached (GENERATION_LIMIT, body carries the
            limit and an upsell object, no Retry-After), the paid daily ceiling
            reached (DAILY_LIMIT_REACHED, body carries the limit that refused,
            20 or 15 for a Pro run, with no Retry-After and no resetAt), or the
            hourly abuse limit (RATE_LIMITED, with Retry-After).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
components:
  schemas:
    GeneratedIdea:
      type: object
      required:
        - name
        - pitch
        - whyNow
        - saturation
        - audience
        - tags
        - sources
      properties:
        name:
          type: string
          maxLength: 100
        pitch:
          type: string
          maxLength: 600
        whyNow:
          type: string
          maxLength: 350
          description: The dated shift that makes the idea timely.
        saturation:
          type: string
          enum:
            - LOW
            - MEDIUM
            - HIGH
        audience:
          type: string
          enum:
            - B2B
            - B2C
            - Both
        tags:
          type: array
          maxItems: 3
          items:
            type: string
        sources:
          type: array
          maxItems: 2
          items:
            type: string
            format: uri
        painPoint:
          type:
            - string
            - 'null'
          maxLength: 250
          description: >
            A real, sourced complaint behind the idea. Null when search found
            none, and null with painPointLocked=true on plans that do not
            include pain points.
        painPointLocked:
          type: boolean
          description: Present only when a pain point exists but the plan hides it.
    ApiError:
      type: object
      required:
        - error
        - code
      description: >
        Every error, on every route, unknown paths included. Some codes add
        top-level context fields (limit, tokenBalance, resetAt, upsell, ...); a
        404 NOT_FOUND on an unknown route adds details.discovery,
        details.openapi and details.docs so the caller can recover.
      properties:
        error:
          type: string
          description: Human-readable message. Prose; it changes.
        code:
          type: string
          description: Stable machine-readable code. Branch on this.
        details:
          description: >
            Optional context. An object on most codes (limits, recovery links,
            the method and path of a 404); an array of strings where the route
            forwards a validator's messages, as enrich does on
            INVALID_FOUNDER_PROFILE. Read it, never branch on it - `code` is the
            contract.
          oneOf:
            - type: object
              additionalProperties: true
            - type: array
              items:
                type: string
        limit:
          type: integer
          description: >
            The ceiling that was hit, in the unit the code implies: bytes on
            PAYLOAD_TOO_LARGE, runs in flight on CONCURRENT_LIMIT_REACHED,
            requests in the window on a rate limit.
        remaining:
          type: integer
          description: Requests left in the current window.
        resetAt:
          type: string
          format: date-time
          description: >
            When the window that refused this request reopens. Present on
            DAILY_LIMIT_REACHED from run creation (the idea generator's rolling
            ceiling sends only limit); prefer it over the Retry-After header
            when both are present, since it is absolute.
        tokenBalance:
          type: integer
          description: >-
            Deep-scan credits left on the account. Present on
            INSUFFICIENT_TOKENS.
        scansUsed:
          type: integer
        scansRemaining:
          type: integer
        limitType:
          type: string
          description: Which ceiling refused an AppSumo-backed account.
        upgradeHint:
          type: string
          description: What to change to get past this refusal. Prose, not a contract.
        paused:
          type: boolean
          description: >
            Present only on the LLM-pause 503, where the body is exactly {
            error, code, paused }.
        upsell:
          $ref: '#/components/schemas/ExportUpsell'
    ExportUpsell:
      type: object
      description: >
        Purchase context for a locked export or buyable quota refusal. Export
        upsells are free-owner only; a paid non-regional caller may receive a
        scan_pack top-up on INSUFFICIENT_TOKENS. Ignore it if you only need the
        data contract.
      properties:
        offer:
          type: string
          enum:
            - founder_report
            - scan_pack
          description: >
            "founder_report" is the $59 one-time tier. "scan_pack" is a top-up
            for an account that already bought and has no deep credits left.
        price:
          type: string
          description: Display price text, e.g. "$59 one-time".
        guarantee:
          type: string
          description: >
            Guarantee text for offer=founder_report. Omitted for scan_pack
            top-ups; an empty string is never used.
        url:
          type: string
          format: uri
          description: Purchase or report URL, with attribution query parameters.
        locked:
          type: array
          description: >
            Section names the offer covers. Distinct from the top-level locked
            object, which names response FIELDS.
          items:
            type: string
        note:
          type: string
          description: The same facts as one sentence, for a client that relays text.
  responses:
    BadRequest:
      description: >
        The request was rejected before any work started: malformed JSON
        (INVALID_JSON), an id that is not one of ours (INVALID_ANALYSIS_ID,
        INVALID_BATCH_ID), an export format we do not serve
        (UNSUPPORTED_FORMAT), or a field the schema cannot express - an idea too
        short to analyse, a batch outside 1..5 items (INVALID_BATCH_SIZE), two
        batch items sharing a clientRunId (DUPLICATE_CLIENT_RUN_ID), an unknown
        scanType. No paid quota, tokens, or scans were consumed. On the CREATE
        routes a rate-limit window can still be: those per-account limiters are
        claimed ahead of schema validation on purpose, so that malformed spam is
        metered, and retrying unchanged spends another slot. Enrich no longer
        works that way - since 2026-09-15 its 12/min window is claimed after the
        body is validated, so a malformed enrich costs nothing. Fix the request
        either way; retrying it unchanged returns the same answer.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiError'
    Unauthorized:
      description: >
        Missing x-preuve-key header (MISSING_API_KEY), or a malformed, unknown
        or revoked key (INVALID_AGENT_KEY - the same envelope for all three, on
        purpose). Repeated failures are throttled per IP and answer 429.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiError'
    PayloadTooLarge:
      description: >
        The request body is over the 1 MB limit (PAYLOAD_TOO_LARGE, with the
        ceiling in `limit`). Counted while the body streams in and refused the
        moment it is exceeded, before any quota, token, or scan is consumed. Not
        retryable unchanged: send less, or split the work across requests.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiError'
    InternalError:
      description: >
        Unexpected server error (INTERNAL_ERROR, or TRIGGER_DISPATCH_FAILED when
        a deep scan could not be handed to the job runner). Never tell the user
        their quota is safe: on a deep scan the spend can already have happened
        when the failure hit, and an ambiguous dispatch deliberately keeps the
        report and its funding because the job may have been accepted before its
        response was lost. Treat consumption as unknown and retry with backoff.
        On an operation that takes a clientRunId, retry with the SAME one - that
        reconciles the original run, whatever state it reached, instead of
        starting and charging for a second one. On every other operation there
        is nothing to reconcile: the read or export simply failed, so a plain
        backed-off retry is the whole recovery.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiError'
    ServiceUnavailable:
      description: >
        A dependency was unavailable or deliberately paused (SERVICE_DISABLED,
        LLM_PAUSED, PROFILE_LOOKUP_FAILED, and the fail-closed answer when the
        rate-limit backend is down). Any authenticated route can answer this
        one: authentication meters itself through the same fail-closed limiter,
        so an outage there refuses the request before the handler runs.
        Retryable, and no paid quota, tokens, or scans were consumed - a pause
        that lands mid-run refunds the claim it had already made. Back off and
        try again rather than falling back to a different endpoint.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiError'
  securitySchemes:
    preuveKey:
      type: apiKey
      in: header
      name: x-preuve-key
      description: >
        Your API key (prv_...), sent as-is. It is the only credential the API
        needs - see the Authentication guide.

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.