> ## 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.

# Export a client report

> Structured JSON for one COMPLETED client report. Starter and refunded reports keep their normal content restrictions, named under locked.




## OpenAPI

````yaml /api-reference/openapi.yaml get /api/agent/agency/reports/{id}/export
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/agency/reports/{id}/export:
    get:
      tags:
        - Agency
      summary: Export a client report
      description: >
        Structured JSON for one COMPLETED client report. Starter and refunded
        reports keep their normal content restrictions, named under locked.
      operationId: exportAgencyReport
      parameters:
        - $ref: '#/components/parameters/AgencyReportId'
        - name: verbosity
          in: query
          required: false
          description: >
            "full" is the whole payload. "summary" drops section prose and
            Trends data points, shortens other text on the largest reports, and
            keeps every score and rating; what it changed is listed under the
            report's details.omitted.
          schema:
            type: string
            enum:
              - full
              - summary
            default: full
      responses:
        '200':
          description: The export payload.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AgencyReportExport'
        '400':
          description: verbosity must be full or summary (INVALID_VERBOSITY).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/AgencyInsufficientScope'
        '404':
          description: >
            Unknown report, or it belongs to another workspace (REPORT_NOT_FOUND
            / AGENCY_NOT_FOUND).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
        '409':
          description: Report is not complete yet (REPORT_NOT_READY).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
        '429':
          $ref: '#/components/responses/RateLimited'
        '500':
          $ref: '#/components/responses/InternalError'
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
components:
  parameters:
    AgencyReportId:
      name: id
      in: path
      required: true
      description: Client report id, scoped to the caller's Agency workspace.
      schema:
        type: string
        format: uuid
  schemas:
    AgencyReportExport:
      type: object
      properties:
        schemaVersion:
          type: integer
        exportType:
          type: string
          enum:
            - agency-report
        dataAsOf:
          type: string
          format: date-time
        exportedAt:
          type: string
          format: date-time
        report:
          type: object
          description: >
            The report payload, same item shape as the ideas-json export. On
            verbosity=summary its details.omitted names what was dropped.
        ownerExclusions:
          type: array
          description: Competitor cards the workspace hid from the client view.
          items:
            type: object
            properties:
              name:
                type: string
              reasonCode:
                type:
                  - string
                  - 'null'
              hiddenAt:
                type:
                  - string
                  - 'null'
                format: date-time
        locked:
          $ref: '#/components/schemas/ExportLocks'
    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'
    ExportLocks:
      type: object
      description: >
        Which fields this export left null, and why. Present only when the
        caller's tier withheld something, and absent entirely when the payload
        is complete. Read it before concluding a field is absent: a null under
        one of these paths is locked, not missing and not a failed analysis. It
        ships to every tier, paid included, and carries no price and no URL. On
        a batch export there is ONE object for the whole batch, the union of
        every exported item's withheld fields, deduplicated by path.
      properties:
        reason:
          type: string
          description: Why the fields were withheld. "starter_scan" today.
        unlockedBy:
          type: string
          description: What lifts the lock. "founder_report" today.
        fields:
          type: array
          description: One entry per withheld field.
          items:
            type: object
            properties:
              path:
                type: string
                description: >
                  Dotted path into the export, e.g. "details.pivots". A path may
                  name several keys of one object with a pipe, e.g.
                  "details.risks[].explanation|mitigation".
              label:
                type: string
                description: >
                  What that path holds, in plain language. Some labels also say
                  the field needs an enrich call once the report is deep.
        note:
          type: string
          description: >
            One sentence restating that the nulls are a tier boundary rather
            than missing data.
    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:
    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'
    AgencyInsufficientScope:
      description: >
        The key is valid but does not carry the one Agency scope the route it
        called requires (INSUFFICIENT_SCOPE). The two are enforced per route and
        neither implies the other: POST /api/agent/agency/analyses requires
        agency:write, and every other Agency route - the workspace context, the
        report list, the report poll and the export - requires agency:read, so
        there is no separate export scope on this surface and a key carrying
        only the other Agency scope is refused here too. Note that every key
        created before 2026-09-09 hits this: these routes previously accepted
        the personal analysis:read / export:read scopes, and scopes are fixed at
        creation rather than backfilled, so an older key needs a new grant.
        Neither route to one is unconditional. Reconnecting the client works on
        any plan, free included, but /authorize only ever intersects the scopes
        the client requests, so a client replaying the scope list it cached at
        registration mints another Agency-less key and revokes the working one
        on the way - check the consent screen lists the Agency permissions
        before approving. Creating a key by hand cannot silently fail, but it
        needs "Include Agency workspace access" ticked AND an account that may
        create keys by hand: a paid personal plan, or membership of a Consultant
        or Agency workspace with an active subscription or a one-time unlock.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ApiError'
    RateLimited:
      description: >
        Rate limited (RATE_LIMITED or DAILY_LIMIT_REACHED). RATE_LIMITED carries
        a Retry-After header with the seconds until the window resets; the
        quota-shaped codes clear on a different clock and put resetAt in the
        body instead.
      headers:
        Retry-After:
          description: Seconds until the per-minute window resets (RATE_LIMITED only).
          schema:
            type: integer
            minimum: 1
      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.